@takosjp/yurucommu-core 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migrations/0019_notification_push_delivery.sql +10 -7
- package/migrations/0020_call_sessions.sql +23 -0
- package/package.json +6 -2
- package/packages/api/package.json +1 -1
- package/packages/api/src/index.ts +1 -0
- package/packages/api/src/lib/api/browser-push.ts +11 -0
- package/packages/api/src/lib/api/normalize.ts +15 -4
- package/packages/api/src/lib/api/notification-target.ts +106 -0
- package/packages/api/src/lib/api/push-config.ts +132 -0
- package/packages/api/src/lib/api.ts +2 -0
- package/packages/api/src/lib/rtc-client.ts +542 -0
- package/packages/api/src/social-server.ts +7 -0
- package/packages/api/src/types/call.ts +306 -0
- package/packages/api/src/types/index.ts +16 -4
- package/src/backend/index.ts +65 -10
- package/src/backend/lib/attachments.ts +52 -0
- package/src/backend/lib/delivery/queue.ts +55 -0
- package/src/backend/lib/notification-eligibility.ts +150 -0
- package/src/backend/lib/notification-push.ts +159 -146
- package/src/backend/lib/rtc/call-store.ts +101 -0
- package/src/backend/lib/rtc/provider.ts +135 -0
- package/src/backend/lib/rtc/signal-transport.ts +125 -0
- package/src/backend/lib/session-actor.ts +16 -1
- package/src/backend/lib/unread-counts.ts +79 -0
- package/src/backend/middleware/csrf.ts +11 -0
- package/src/backend/public.ts +3 -0
- package/src/backend/routes/account-teardown.ts +13 -0
- package/src/backend/routes/activitypub.ts +5 -1
- package/src/backend/routes/auth-helpers.ts +10 -7
- package/src/backend/routes/auth.ts +131 -3
- package/src/backend/routes/communities/messages.ts +16 -33
- package/src/backend/routes/dm/contacts.ts +6 -44
- package/src/backend/routes/dm/messages.ts +5 -39
- package/src/backend/routes/notifications.ts +27 -70
- package/src/backend/routes/posts/transformers.ts +8 -10
- package/src/backend/routes/rtc/index.ts +147 -0
- package/src/backend/runtime/call-hub-core.ts +470 -0
- package/src/backend/runtime/call-hub-port.ts +78 -0
- package/src/backend/runtime/call-signaling-do.ts +242 -0
- package/src/backend/runtime/signaling-hub.ts +187 -0
- package/src/backend/types.ts +28 -0
- package/src/db/index.ts +11 -10
- package/src/db/schema/calls.ts +46 -0
- package/src/db/schema/index.ts +1 -0
- package/src/db/schema/mobile.ts +7 -9
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
-- NO foreign keys on these tables. Cloudflare D1 ENFORCES declared FKs (see
|
|
2
|
+
-- 0010/0011, which dropped the actors FKs for exactly that reason), local
|
|
3
|
+
-- libsql runs with enforcement OFF, and the trigger below fires on EVERY
|
|
4
|
+
-- unread inbox insert — an actors FK here would let a single missing actors
|
|
5
|
+
-- row abort unrelated inbox writes in production only. Referential cleanup is
|
|
6
|
+
-- app-level, like everywhere else (routes/account-teardown.ts).
|
|
1
7
|
CREATE TABLE IF NOT EXISTS notification_pushers (
|
|
2
8
|
id TEXT PRIMARY KEY,
|
|
3
9
|
actor_ap_id TEXT NOT NULL,
|
|
@@ -15,13 +21,11 @@ CREATE TABLE IF NOT EXISTS notification_pushers (
|
|
|
15
21
|
gateway_url TEXT NOT NULL,
|
|
16
22
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
17
23
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
18
|
-
last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
19
|
-
FOREIGN KEY (actor_ap_id) REFERENCES actors(ap_id) ON DELETE CASCADE
|
|
24
|
+
last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
20
25
|
);
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
-- Device uniqueness is (product, app_id, pushkey_hash) — strictly stronger
|
|
28
|
+
-- than any actor-scoped variant, so no second unique index is declared.
|
|
25
29
|
CREATE INDEX IF NOT EXISTS notification_pushers_actor_product_idx
|
|
26
30
|
ON notification_pushers(actor_ap_id, product);
|
|
27
31
|
|
|
@@ -44,8 +48,7 @@ CREATE TABLE IF NOT EXISTS notification_push_jobs (
|
|
|
44
48
|
last_error TEXT,
|
|
45
49
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
46
50
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
47
|
-
delivered_at TEXT
|
|
48
|
-
FOREIGN KEY (actor_ap_id) REFERENCES actors(ap_id) ON DELETE CASCADE
|
|
51
|
+
delivered_at TEXT
|
|
49
52
|
);
|
|
50
53
|
|
|
51
54
|
CREATE UNIQUE INDEX IF NOT EXISTS notification_push_jobs_actor_activity_idx
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS call_sessions (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
local_actor_ap_id TEXT NOT NULL,
|
|
4
|
+
peer_actor_ap_id TEXT NOT NULL,
|
|
5
|
+
direction TEXT NOT NULL,
|
|
6
|
+
state TEXT NOT NULL DEFAULT 'ringing',
|
|
7
|
+
media_audio INTEGER NOT NULL DEFAULT 1,
|
|
8
|
+
media_video INTEGER NOT NULL DEFAULT 0,
|
|
9
|
+
sfu_focus TEXT,
|
|
10
|
+
peer_signal_endpoint TEXT,
|
|
11
|
+
end_reason TEXT,
|
|
12
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
13
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
14
|
+
connected_at TEXT,
|
|
15
|
+
ended_at TEXT,
|
|
16
|
+
FOREIGN KEY (local_actor_ap_id) REFERENCES actors(ap_id) ON DELETE CASCADE
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE INDEX IF NOT EXISTS call_sessions_local_created_idx
|
|
20
|
+
ON call_sessions(local_actor_ap_id, created_at);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS call_sessions_state_idx
|
|
23
|
+
ON call_sessions(state);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takosjp/yurucommu-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"packages/api/src/types",
|
|
20
20
|
"packages/api/src/lib/api.ts",
|
|
21
21
|
"packages/api/src/lib/transport.ts",
|
|
22
|
+
"packages/api/src/lib/rtc-client.ts",
|
|
22
23
|
"packages/api/src/lib/fetch-with-timeout.ts",
|
|
23
24
|
"packages/api/src/lib/api/account.ts",
|
|
24
25
|
"packages/api/src/lib/api/actors.ts",
|
|
@@ -32,8 +33,10 @@
|
|
|
32
33
|
"packages/api/src/lib/api/moderation.ts",
|
|
33
34
|
"packages/api/src/lib/api/notes.ts",
|
|
34
35
|
"packages/api/src/lib/api/normalize.ts",
|
|
36
|
+
"packages/api/src/lib/api/notification-target.ts",
|
|
35
37
|
"packages/api/src/lib/api/notifications.ts",
|
|
36
38
|
"packages/api/src/lib/api/posts.ts",
|
|
39
|
+
"packages/api/src/lib/api/push-config.ts",
|
|
37
40
|
"packages/api/src/lib/api/recommendations.ts",
|
|
38
41
|
"packages/api/src/lib/api/search.ts",
|
|
39
42
|
"packages/api/src/lib/api/stories.ts",
|
|
@@ -56,7 +59,8 @@
|
|
|
56
59
|
"start": "bun src/backend/server.ts",
|
|
57
60
|
"dev": "bun src/backend/server.ts",
|
|
58
61
|
"dev:server": "bun src/backend/server.ts",
|
|
59
|
-
"check": "tsc --noEmit",
|
|
62
|
+
"check": "tsc --noEmit && bun run check:no-opentofu-artifacts",
|
|
63
|
+
"check:no-opentofu-artifacts": "bun scripts/check-no-opentofu-artifacts.mjs",
|
|
60
64
|
"test": "bun run build:api && bun test test/ src/backend/ packages/api/src/ scripts/check-publish-version-discipline.test.ts scripts/publish-package-resumable.test.ts && bun run check:release-contents",
|
|
61
65
|
"test:backend": "bun test src/backend/",
|
|
62
66
|
"build": "bun run build:api",
|
|
@@ -294,6 +294,17 @@ function normalizeServerOrigin(value: string): string | null {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Client-side gateway URL policy. MUST stay behaviorally equivalent to the
|
|
299
|
+
* server's `normalizeGatewayUrl` in the notification pusher contract — a
|
|
300
|
+
* client-accepted / server-rejected URL yields a confusing "registers but
|
|
301
|
+
* 400s" failure. The equivalence is pinned by a shared-fixture drift test.
|
|
302
|
+
* Exported for that test; app code calls the higher-level helpers above.
|
|
303
|
+
*/
|
|
304
|
+
export function normalizeBrowserPushGatewayUrl(value: string): string | null {
|
|
305
|
+
return normalizeGatewayUrl(value);
|
|
306
|
+
}
|
|
307
|
+
|
|
297
308
|
function normalizeGatewayUrl(value: string): string | null {
|
|
298
309
|
try {
|
|
299
310
|
const url = new URL(value.trim());
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Post,
|
|
7
7
|
Story,
|
|
8
8
|
} from "../../types/index.ts";
|
|
9
|
+
import { resolveNotificationTarget } from "./notification-target.ts";
|
|
9
10
|
|
|
10
11
|
type ActorLike = {
|
|
11
12
|
ap_id: string;
|
|
@@ -71,7 +72,17 @@ export const normalizeActorNote = (note: ActorNote): ActorNote => ({
|
|
|
71
72
|
|
|
72
73
|
export const normalizeNotification = (
|
|
73
74
|
notification: Notification,
|
|
74
|
-
): Notification =>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
): Notification => {
|
|
76
|
+
// Fill the navigation target so consumers get a stable, safe same-origin
|
|
77
|
+
// path even from a pre-3.2.0 server that omitted target_* (or sent an unsafe
|
|
78
|
+
// target_url). resolveNotificationTarget re-validates a declared target and
|
|
79
|
+
// otherwise synthesizes it from type + object_ap_id.
|
|
80
|
+
const target = resolveNotificationTarget(notification);
|
|
81
|
+
return {
|
|
82
|
+
...notification,
|
|
83
|
+
actor: normalizeActor(notification.actor),
|
|
84
|
+
target_kind: target.target_kind,
|
|
85
|
+
target_id: target.target_id,
|
|
86
|
+
target_url: target.target_url,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Notification,
|
|
3
|
+
NotificationTargetKind,
|
|
4
|
+
} from "../../types/index.ts";
|
|
5
|
+
|
|
6
|
+
export interface NotificationTarget {
|
|
7
|
+
readonly target_kind: NotificationTargetKind;
|
|
8
|
+
readonly target_id: string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Same-origin in-app path shaped for the yurucommu web client's routing.
|
|
11
|
+
* Other clients should prefer `target_kind` + `target_id` and build their own
|
|
12
|
+
* path. Never treat this as an external URL.
|
|
13
|
+
*/
|
|
14
|
+
readonly target_url: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** True if the string contains any C0 control character (0x00-0x1f). */
|
|
18
|
+
function hasControlChar(value: string): boolean {
|
|
19
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
20
|
+
if (value.charCodeAt(index) < 0x20) return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Guard an in-app navigation path against open-redirect / protocol-relative /
|
|
27
|
+
* control-character abuse. Returns the value only if it is a plain same-origin
|
|
28
|
+
* absolute path (`/...`, not `//...`), else null. Promoted from the yurucommu
|
|
29
|
+
* and yurume web clients, which each carried an identical copy.
|
|
30
|
+
*/
|
|
31
|
+
export function safeNotificationPath(
|
|
32
|
+
value: string | null | undefined,
|
|
33
|
+
): string | null {
|
|
34
|
+
if (
|
|
35
|
+
!value ||
|
|
36
|
+
!value.startsWith("/") ||
|
|
37
|
+
value.startsWith("//") ||
|
|
38
|
+
value.includes("\\") ||
|
|
39
|
+
hasControlChar(value)
|
|
40
|
+
) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isStoryNotification(notification: Notification): boolean {
|
|
47
|
+
return (
|
|
48
|
+
notification.target_kind === "story" ||
|
|
49
|
+
!!notification.object_ap_id?.includes("/ap/stories/")
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the navigation target for a notification. Prefers the server-provided
|
|
55
|
+
* `target_*` fields (3.2.0+) after re-validating `target_url`; when a server
|
|
56
|
+
* older than 3.2.0 omitted them, synthesizes the same mapping the server uses
|
|
57
|
+
* from `type` + `object_ap_id`. Always returns a safe same-origin path.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveNotificationTarget(
|
|
60
|
+
notification: Notification,
|
|
61
|
+
): NotificationTarget {
|
|
62
|
+
const declaredKind = notification.target_kind;
|
|
63
|
+
const declaredUrl = safeNotificationPath(notification.target_url);
|
|
64
|
+
if (declaredKind && declaredUrl) {
|
|
65
|
+
return {
|
|
66
|
+
target_kind: declaredKind,
|
|
67
|
+
target_id: notification.target_id ?? null,
|
|
68
|
+
target_url: declaredUrl,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
notification.type === "follow" ||
|
|
74
|
+
notification.type === "follow_request"
|
|
75
|
+
) {
|
|
76
|
+
const actorApId = notification.actor?.ap_id ?? null;
|
|
77
|
+
return {
|
|
78
|
+
target_kind: "profile",
|
|
79
|
+
target_id: actorApId,
|
|
80
|
+
target_url: actorApId
|
|
81
|
+
? `/profile/${encodeURIComponent(actorApId)}`
|
|
82
|
+
: "/notifications",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const objectApId = notification.object_ap_id;
|
|
87
|
+
if (objectApId && isStoryNotification(notification)) {
|
|
88
|
+
return {
|
|
89
|
+
target_kind: "story",
|
|
90
|
+
target_id: objectApId,
|
|
91
|
+
target_url: `/?story=${encodeURIComponent(objectApId)}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (objectApId) {
|
|
95
|
+
return {
|
|
96
|
+
target_kind: "post",
|
|
97
|
+
target_id: objectApId,
|
|
98
|
+
target_url: `/post/${encodeURIComponent(objectApId)}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
target_kind: "notifications",
|
|
103
|
+
target_id: null,
|
|
104
|
+
target_url: "/notifications",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { NotificationPusherProduct } from "../../types/index.ts";
|
|
2
|
+
import {
|
|
3
|
+
clearBrowserNotificationPush,
|
|
4
|
+
disableBrowserNotificationPush,
|
|
5
|
+
type BrowserNotificationPushConfig,
|
|
6
|
+
} from "./browser-push.ts";
|
|
7
|
+
import { fetchNotificationPusherPublicConfig } from "./notifications.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stable identity of a browser/PWA push client. Everything here is a per-app
|
|
11
|
+
* constant; only the gateway URL, VAPID key, and server origin are resolved at
|
|
12
|
+
* runtime.
|
|
13
|
+
*/
|
|
14
|
+
export interface BrowserPushClientIdentity {
|
|
15
|
+
readonly product: NotificationPusherProduct;
|
|
16
|
+
readonly appId: string;
|
|
17
|
+
readonly appDisplayName: string;
|
|
18
|
+
/** Root-relative service worker script path (e.g. "/notification-push-sw.js"). */
|
|
19
|
+
readonly serviceWorkerPath: string;
|
|
20
|
+
readonly scope?: string;
|
|
21
|
+
readonly lang?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Non-secret runtime gateway values, from a fetch or build-time fallback. */
|
|
25
|
+
export interface BrowserPushRuntimeValues {
|
|
26
|
+
readonly gatewayUrl?: string | null;
|
|
27
|
+
readonly vapidPublicKey?: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BrowserPushConfigResolverOptions {
|
|
31
|
+
readonly identity: BrowserPushClientIdentity;
|
|
32
|
+
/**
|
|
33
|
+
* Origin of the yurucommu-compatible API that owns the registration. Returns
|
|
34
|
+
* null when it cannot be determined (e.g. SSR, or an unconfigured shell), in
|
|
35
|
+
* which case config resolution yields null.
|
|
36
|
+
*/
|
|
37
|
+
readonly resolveServerOrigin: () => string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Build-time fallback used only while a server rolls forward to 3.2.0; a
|
|
40
|
+
* responding runtime config always wins.
|
|
41
|
+
*/
|
|
42
|
+
readonly buildTimeValues?: () => BrowserPushRuntimeValues | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BrowserPushConfigResolver {
|
|
46
|
+
/** Build config from build-time values only (no network). */
|
|
47
|
+
readonly buildTimeConfig: () => BrowserNotificationPushConfig | null;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve config from the server's runtime pusher config, falling back to
|
|
50
|
+
* build-time values if the server is unreachable or has push disabled.
|
|
51
|
+
*/
|
|
52
|
+
readonly resolveConfig: () => Promise<BrowserNotificationPushConfig | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Invalidate this device's endpoint before sign-out / account teardown,
|
|
55
|
+
* disabling via the resolved config when possible and always clearing the
|
|
56
|
+
* local subscription so a signed-out device is never woken.
|
|
57
|
+
*/
|
|
58
|
+
readonly clearBeforeSignOut: () => Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build a browser push config resolver for a single client identity. Promoted
|
|
63
|
+
* from the near-identical per-app resolvers in the yurucommu and yurume web
|
|
64
|
+
* clients so the fetch/fallback/clear flow lives in one place.
|
|
65
|
+
*/
|
|
66
|
+
export function createBrowserPushConfigResolver(
|
|
67
|
+
options: BrowserPushConfigResolverOptions,
|
|
68
|
+
): BrowserPushConfigResolver {
|
|
69
|
+
const { identity, resolveServerOrigin, buildTimeValues } = options;
|
|
70
|
+
|
|
71
|
+
const build = (
|
|
72
|
+
values: BrowserPushRuntimeValues | null | undefined,
|
|
73
|
+
): BrowserNotificationPushConfig | null => {
|
|
74
|
+
const gatewayUrl = values?.gatewayUrl?.trim();
|
|
75
|
+
const vapidPublicKey = values?.vapidPublicKey?.trim();
|
|
76
|
+
if (!gatewayUrl || !vapidPublicKey) return null;
|
|
77
|
+
const serverOrigin = resolveServerOrigin();
|
|
78
|
+
if (!serverOrigin) return null;
|
|
79
|
+
return {
|
|
80
|
+
product: identity.product,
|
|
81
|
+
appId: identity.appId,
|
|
82
|
+
appDisplayName: identity.appDisplayName,
|
|
83
|
+
serverOrigin,
|
|
84
|
+
gatewayUrl,
|
|
85
|
+
vapidPublicKey,
|
|
86
|
+
serviceWorkerPath: identity.serviceWorkerPath,
|
|
87
|
+
...(identity.scope ? { scope: identity.scope } : {}),
|
|
88
|
+
...(identity.lang ? { lang: identity.lang } : {}),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const buildTimeConfig = (): BrowserNotificationPushConfig | null =>
|
|
93
|
+
build(buildTimeValues?.() ?? null);
|
|
94
|
+
|
|
95
|
+
const resolveConfig =
|
|
96
|
+
async (): Promise<BrowserNotificationPushConfig | null> => {
|
|
97
|
+
try {
|
|
98
|
+
const runtime = await fetchNotificationPusherPublicConfig();
|
|
99
|
+
if (
|
|
100
|
+
!runtime.enabled ||
|
|
101
|
+
!runtime.gateway_url ||
|
|
102
|
+
!runtime.web_push_public_key
|
|
103
|
+
) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return build({
|
|
107
|
+
gatewayUrl: runtime.gateway_url,
|
|
108
|
+
vapidPublicKey: runtime.web_push_public_key,
|
|
109
|
+
});
|
|
110
|
+
} catch {
|
|
111
|
+
// Compatibility with older servers while they roll forward. Build-time
|
|
112
|
+
// public values are a fallback only; a responding runtime config is the
|
|
113
|
+
// deployment authority.
|
|
114
|
+
return buildTimeConfig();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const clearBeforeSignOut = async (): Promise<void> => {
|
|
119
|
+
const config = await resolveConfig().catch(() => null);
|
|
120
|
+
if (config) {
|
|
121
|
+
try {
|
|
122
|
+
await disableBrowserNotificationPush(config);
|
|
123
|
+
return;
|
|
124
|
+
} catch {
|
|
125
|
+
// Fall through to local endpoint invalidation.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
await clearBrowserNotificationPush(identity).catch(() => undefined);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return { buildTimeConfig, resolveConfig, clearBeforeSignOut };
|
|
132
|
+
}
|
|
@@ -7,7 +7,9 @@ export * from "./api/posts.ts";
|
|
|
7
7
|
export * from "./api/communities.ts";
|
|
8
8
|
export * from "./api/dm.ts";
|
|
9
9
|
export * from "./api/notifications.ts";
|
|
10
|
+
export * from "./api/notification-target.ts";
|
|
10
11
|
export * from "./api/browser-push.ts";
|
|
12
|
+
export * from "./api/push-config.ts";
|
|
11
13
|
export * from "./api/search.ts";
|
|
12
14
|
export * from "./api/media.ts";
|
|
13
15
|
export * from "./api/stories.ts";
|