@mulmobridge/web-push 0.1.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/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @mulmobridge/web-push
2
+
3
+ Auth-agnostic sender for the **mulmoserver `sendPush` Cloud Function**. The app only
4
+ POSTs `{ title, body }` + a Firebase Auth ID token; the target devices resolve
5
+ server-side from the signed-in user's uid, and device registration / delivery /
6
+ dead-token pruning are the server's job (see mulmoserver `docs/web-push-sending.md`).
7
+
8
+ No firebase or app dependency — the caller injects the ID-token provider, so both
9
+ mulmoclaude and mulmoterminal share one source of truth for the wire contract.
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { sendWebPush } from "@mulmobridge/web-push";
15
+
16
+ const result = await sendWebPush("✅ my-project", "Task finished", {
17
+ // Return the current Firebase Auth ID token, or null when not signed in.
18
+ getIdToken: async () => auth.currentUser?.getIdToken() ?? null,
19
+ });
20
+ // → { sent, failed, targets } on success, or null when nothing was sent
21
+ // (not signed in / network / timeout / non-2xx). Never throws.
22
+ // targets === 0 means the user hasn't enabled notifications on any device.
23
+ ```
24
+
25
+ ## API
26
+
27
+ - `sendWebPush(title, body, options)` — POST to `sendPush`. No-ops (returns `null`,
28
+ never fetches) when `getIdToken()` yields `null` or rejects. `AbortController`
29
+ timeout (default 8000 ms). Never throws.
30
+ - `buildSendPushBody(title, body)` — the onCall `{ data: { title, body } }` envelope.
31
+ - `parseSendPushResult(json)` — read `{ sent, failed, targets }` from the onCall
32
+ `{ result }` envelope, or `null` when the shape doesn't match.
33
+ - `DEFAULT_SEND_PUSH_URL` — the mulmoserver production endpoint.
34
+
35
+ ### `SendWebPushOptions`
36
+
37
+ | Field | Default | Purpose |
38
+ |---|---|---|
39
+ | `getIdToken` | — (required) | `() => Promise<string \| null>`; `null` ⇒ skip |
40
+ | `url` | `DEFAULT_SEND_PUSH_URL` | sendPush endpoint |
41
+ | `timeoutMs` | `8000` | request timeout |
42
+ | `fetchImpl` | `globalThis.fetch` | test seam |
43
+
44
+ ## License
45
+
46
+ MIT
@@ -0,0 +1,15 @@
1
+ export declare const DEFAULT_SEND_PUSH_URL = "https://asia-northeast1-mulmoserver.cloudfunctions.net/sendPush";
2
+ export interface SendPushResult {
3
+ sent: number;
4
+ failed: number;
5
+ targets: number;
6
+ }
7
+ export interface SendWebPushOptions {
8
+ getIdToken: () => Promise<string | null>;
9
+ url?: string;
10
+ timeoutMs?: number;
11
+ fetchImpl?: typeof fetch;
12
+ }
13
+ export declare function buildSendPushBody(title: string, body: string): string;
14
+ export declare function parseSendPushResult(json: unknown): SendPushResult | null;
15
+ export declare function sendWebPush(title: string, body: string, options: SendWebPushOptions): Promise<SendPushResult | null>;
package/dist/index.js ADDED
@@ -0,0 +1,65 @@
1
+ // @mulmobridge/web-push — send a Web Push via the mulmoserver `sendPush`
2
+ // Cloud Function (see mulmoserver docs/web-push-sending.md).
3
+ //
4
+ // Auth-agnostic: the caller injects an ID-token provider, so this has no
5
+ // firebase / app dependency and both mulmoclaude and mulmoterminal share it.
6
+ // We only POST { title, body }; the target devices resolve server-side from the
7
+ // signed-in user's uid, and registration / delivery / dead-token pruning are
8
+ // the server's job.
9
+ // asia-northeast1 onCall endpoint for the `mulmoserver` project.
10
+ export const DEFAULT_SEND_PUSH_URL = "https://asia-northeast1-mulmoserver.cloudfunctions.net/sendPush";
11
+ const DEFAULT_TIMEOUT_MS = 8000;
12
+ // The onCall wire shape wraps the payload in `data`.
13
+ export function buildSendPushBody(title, body) {
14
+ return JSON.stringify({ data: { title, body } });
15
+ }
16
+ // The onCall response wraps the payload in `result`. Missing / non-number counts read as 0.
17
+ export function parseSendPushResult(json) {
18
+ if (typeof json !== "object" || json === null)
19
+ return null;
20
+ const result = json.result;
21
+ if (typeof result !== "object" || result === null)
22
+ return null;
23
+ const record = result;
24
+ const num = (value) => (typeof value === "number" ? value : 0);
25
+ return { sent: num(record.sent), failed: num(record.failed), targets: num(record.targets) };
26
+ }
27
+ // getIdToken can itself throw (auth SDK). Treat any failure as "not signed in".
28
+ async function resolveIdToken(getIdToken) {
29
+ try {
30
+ return await getIdToken();
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ // POST { title, body } to sendPush as the signed-in user. Returns the delivery
37
+ // result, or null when nothing was sent (not signed in / network / timeout /
38
+ // non-2xx / bad JSON). Never throws — a failed push must not disturb its trigger.
39
+ export async function sendWebPush(title, body, options) {
40
+ const idToken = await resolveIdToken(options.getIdToken);
41
+ if (!idToken)
42
+ return null; // not signed in → nothing to send with
43
+ const url = options.url ?? DEFAULT_SEND_PUSH_URL;
44
+ const timeout_ms = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
45
+ const doFetch = options.fetchImpl ?? globalThis.fetch;
46
+ const controller = new AbortController();
47
+ const timer = setTimeout(() => controller.abort(), timeout_ms);
48
+ try {
49
+ const res = await doFetch(url, {
50
+ method: "POST",
51
+ headers: { "content-type": "application/json", authorization: `Bearer ${idToken}` },
52
+ body: buildSendPushBody(title, body),
53
+ signal: controller.signal,
54
+ });
55
+ if (!res.ok)
56
+ return null;
57
+ return parseSendPushResult(await res.json());
58
+ }
59
+ catch {
60
+ return null; // offline / aborted / bad JSON — silently skip
61
+ }
62
+ finally {
63
+ clearTimeout(timer);
64
+ }
65
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@mulmobridge/web-push",
3
+ "version": "0.1.0",
4
+ "description": "Auth-agnostic sender for the mulmoserver Web Push (sendPush) Cloud Function",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "prepack": "yarn build",
23
+ "typecheck": "tsc --noEmit",
24
+ "test": "tsx --test test/test_*.ts",
25
+ "lint": "eslint src test"
26
+ },
27
+ "license": "MIT",
28
+ "author": "Receptron Team",
29
+ "devDependencies": {
30
+ "typescript": "^6.0.3"
31
+ }
32
+ }