start adding webpush and change index api

This commit is contained in:
murdle 2025-12-06 18:46:59 +02:00
parent 647649f9d9
commit d237044040
11 changed files with 140 additions and 13 deletions

View File

@ -30,12 +30,12 @@
async function loadGlobalEnv() {
try {
const res = await fetch("/api/v9/policies/instance/domains");
const res = await fetch("/.well-known/spacebar/client");
const data = await res.json();
window.GLOBAL_ENV = {
API_ENDPOINT: protocolRelative(data.apiEndpoint),
WEBAPP_ENDPOINT: protocolRelative(data.apiEndpoint.replace(/\/api$/, '')),
API_ENDPOINT: protocolRelative(data.api.baseUrl) + "/api/v9",
WEBAPP_ENDPOINT: protocolRelative(data.api.baseUrl),
CDN_HOST: protocolRelative(data.cdn),
ASSET_ENDPOINT: protocolRelative(data.cdn),
MEDIA_PROXY_ENDPOINT: protocolRelative(data.cdn),
@ -44,17 +44,17 @@
GUILD_TEMPLATE_HOST: 'discord.new',
GIFT_CODE_HOST: 'discord.gift',
RELEASE_CHANNEL: 'stable',
MARKETING_ENDPOINT: protocolRelative(data.apiEndpoint.replace(/\/api$/, '')),
MARKETING_ENDPOINT: protocolRelative(data.api.baseUrl),
BRAINTREE_KEY: 'production_5st77rrc_49pp2rp4phym7387',
STRIPE_KEY: 'pk_live_CUQtlpQUF0vufWpnpUmQvcdi',
NETWORKING_ENDPOINT: protocolRelative(data.apiEndpoint.replace(/\/api$/, '')),
RTC_LATENCY_ENDPOINT: protocolRelative(data.apiEndpoint.replace(/\/api$/, '')),
ACTIVITY_APPLICATION_HOST: protocolRelative(data.apiEndpoint.replace(/\/api$/, '')),
NETWORKING_ENDPOINT: protocolRelative(data.api.baseUrl),
RTC_LATENCY_ENDPOINT: protocolRelative(data.api.baseUrl),
ACTIVITY_APPLICATION_HOST: protocolRelative(data.api.baseUrl),
PROJECT_ENV: 'production',
REMOTE_AUTH_ENDPOINT: '//remote-auth-gateway.discord.gg',
SENTRY_TAGS: { buildId: '9af39da', buildType: 'normal' },
MIGRATION_SOURCE_ORIGIN: protocolRelative(data.apiEndpoint.replace(/\/api$/, '')),
MIGRATION_DESTINATION_ORIGIN: protocolRelative(data.apiEndpoint.replace(/\/api$/, '')),
MIGRATION_SOURCE_ORIGIN: protocolRelative(data.api.baseUrl),
MIGRATION_DESTINATION_ORIGIN: protocolRelative(data.api.baseUrl),
HTML_TIMESTAMP: Date.now(),
ALGOLIA_KEY: 'aca0d7082e4e63af5ba5917d5e96bed0',
GATEWAY_URL: data.gateway

BIN
package-lock.json generated

Binary file not shown.

View File

@ -75,6 +75,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.936.0",
"@toondepauw/node-zstd": "^1.2.0",
"@types/web-push": "^3.6.4",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"amqplib": "^0.10.9",
@ -113,6 +114,7 @@
"sqlite3": "^5.1.7",
"tslib": "^2.8.1",
"typeorm": "^0.3.27",
"web-push": "^3.6.7",
"wretch": "^2.11.1",
"ws": "^8.18.3"
},

View File

@ -25,6 +25,7 @@ import path from "path";
import express from "express";
import { red } from "picocolors";
import { initInstance } from "./util/handlers/Instance";
import { configurePush } from "../util/util/WebPush";
const ASSETS_FOLDER = path.join(__dirname, "..", "..", "assets");
const PUBLIC_ASSETS_FOLDER = path.join(ASSETS_FOLDER, "public");
@ -57,6 +58,7 @@ export class SpacebarServer extends Server {
await ConnectionConfig.init();
await initInstance();
WebAuthn.init();
configurePush();
const logRequests = process.env["LOG_REQUESTS"] != undefined;
if (logRequests) {
@ -117,8 +119,6 @@ export class SpacebarServer extends Server {
res.sendFile(path.join(ASSETS_FOLDER, "openapi.json"));
});
app.use("*_", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")));
// current well-known location (new commit 22 nov 2025 from spacebar)
app.get("/.well-known/spacebar", (req, res) => {
res.json({
@ -155,6 +155,8 @@ export class SpacebarServer extends Server {
});
});
app.use("*_", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")));
this.app.use(ErrorHandler);
ConnectionLoader.loadConnections();

View File

@ -37,6 +37,7 @@ import {
SecurityConfiguration,
TemplateConfiguration,
UserConfiguration,
WebPushConfiguration,
} from "../config";
export class ConfigValue {
@ -58,7 +59,7 @@ export class ConfigValue {
defaults: DefaultsConfiguration = new DefaultsConfiguration();
external: ExternalTokensConfiguration = new ExternalTokensConfiguration();
email: EmailConfiguration = new EmailConfiguration();
passwordReset: PasswordResetConfiguration =
new PasswordResetConfiguration();
passwordReset: PasswordResetConfiguration = new PasswordResetConfiguration();
user: UserConfiguration = new UserConfiguration();
webPush: WebPushConfiguration = new WebPushConfiguration();
}

View File

@ -0,0 +1,24 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export class WebPushConfiguration {
enabled: boolean = false;
email: string | null = null;
publicVapidKey: string | null = null;
privateVapidKey: string | null = null;
}

View File

@ -37,3 +37,4 @@ export * from "./SecurityConfiguration";
export * from "./subconfigurations";
export * from "./TemplateConfiguration";
export * from "./UsersConfiguration";
export * from "./WebPushConfiguration";

View File

@ -0,0 +1,26 @@
import { BaseClass } from "./BaseClass";
import { User } from "./User";
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm";
@Entity({ name: "push_subscriptions" })
export class PushSubscription extends BaseClass {
@Column({ type: "varchar", nullable: true })
@RelationId((sub: PushSubscription) => sub.user)
user_id: string;
@JoinColumn({ name: "user_id" })
@ManyToOne(() => User, { onDelete: "SET NULL" })
user: User;
@Column({ type: "varchar", nullable: false })
endpoint: string;
@Column({ type: "bigint", nullable: true })
expiration_time?: number;
@Column({ type: "varchar", nullable: false })
auth: string;
@Column({ type: "varchar", nullable: false })
p256dh: string;
}

View File

@ -0,0 +1,36 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class PushSubscriptions1764870551028 implements MigrationInterface {
name = "PushSubscriptions1764870551028";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "push_subscriptions" (
"id" character varying NOT NULL,
"user_id" character varying,
"endpoint" character varying NOT NULL,
"expiration_time" bigint,
"auth" character varying NOT NULL,
"p256dh" character varying NOT NULL,
CONSTRAINT "PK_push_subscriptions_id" PRIMARY KEY ("id"),
CONSTRAINT "UQ_push_subscriptions_endpoint" UNIQUE ("endpoint")
);
`);
await queryRunner.query(`
ALTER TABLE "push_subscriptions"
ADD CONSTRAINT "FK_push_subscriptions_user"
FOREIGN KEY ("user_id") REFERENCES "users"("id")
ON DELETE SET NULL ON UPDATE NO ACTION;
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "push_subscriptions"
DROP CONSTRAINT "FK_push_subscriptions_user";
`);
await queryRunner.query(`DROP TABLE "push_subscriptions"`);
}
}

35
src/util/util/WebPush.ts Normal file
View File

@ -0,0 +1,35 @@
import { Config } from "@spacebar/util";
import { yellow } from "picocolors";
import { PushSubscription } from "../entities/PushSubscription";
import webpush, { PushSubscription as WebPushSubscription } from "web-push";
let vapidConfigured = false;
export function configurePush() {
const { enabled, email, publicVapidKey, privateVapidKey } = Config.get().webPush;
if (!enabled) {
vapidConfigured = false;
return;
}
if (!email || !publicVapidKey || !privateVapidKey) {
console.warn("[WebPush]", yellow("VAPID details are missing. Push notifications will be disabled."));
vapidConfigured = false;
return;
}
webpush.setVapidDetails(email, publicVapidKey, privateVapidKey);
vapidConfigured = true;
}
export function parseSubscription(result: PushSubscription): WebPushSubscription {
return {
endpoint: result.endpoint,
expirationTime: result.expiration_time ?? null,
keys: {
p256dh: result.p256dh,
auth: result.auth,
},
};
}