@juspay/shooter 1.24.2 → 1.25.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/.claude/hooks/notifier.cjs +32 -4
- package/build/client/_app/immutable/assets/{4.D4EDSN4H.css → 4.ChO_hlLs.css} +1 -1
- package/build/client/_app/immutable/assets/4.ChO_hlLs.css.br +0 -0
- package/build/client/_app/immutable/assets/4.ChO_hlLs.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{BBuzUXZ9.js → BstJSK2K.js} +1 -1
- package/build/client/_app/immutable/chunks/BstJSK2K.js.br +0 -0
- package/build/client/_app/immutable/chunks/BstJSK2K.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{Dqmsaccg.js → DINqYbXU.js} +1 -1
- package/build/client/_app/immutable/chunks/DINqYbXU.js.br +0 -0
- package/build/client/_app/immutable/chunks/DINqYbXU.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ssAhjWfF.js +3 -0
- package/build/client/_app/immutable/chunks/ssAhjWfF.js.br +0 -0
- package/build/client/_app/immutable/chunks/ssAhjWfF.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.Bu4nq_bk.js → app.BPK9s2o5.js} +2 -2
- package/build/client/_app/immutable/entry/app.BPK9s2o5.js.br +0 -0
- package/build/client/_app/immutable/entry/app.BPK9s2o5.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DFPHuGE3.js +1 -0
- package/build/client/_app/immutable/entry/start.DFPHuGE3.js.br +2 -0
- package/build/client/_app/immutable/entry/start.DFPHuGE3.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.CnSSjKHX.js → 0.BEbzRcwm.js} +1 -1
- package/build/client/_app/immutable/nodes/0.BEbzRcwm.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BEbzRcwm.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.D_5wZINa.js → 1.C_V0SOr9.js} +1 -1
- package/build/client/_app/immutable/nodes/1.C_V0SOr9.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.C_V0SOr9.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{10.C_B2eMms.js → 10.6V-37_Rl.js} +1 -1
- package/build/client/_app/immutable/nodes/10.6V-37_Rl.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.6V-37_Rl.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{11.DUKnn5ja.js → 11.Lzf-KC6L.js} +1 -1
- package/build/client/_app/immutable/nodes/11.Lzf-KC6L.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.Lzf-KC6L.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.C37CZKYv.js → 2.Cl2dMP2L.js} +1 -1
- package/build/client/_app/immutable/nodes/2.Cl2dMP2L.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.Cl2dMP2L.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.C48G514u.js → 3.CuX2eSna.js} +1 -1
- package/build/client/_app/immutable/nodes/3.CuX2eSna.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CuX2eSna.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.2DvqoOaB.js +17 -0
- package/build/client/_app/immutable/nodes/4.2DvqoOaB.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.2DvqoOaB.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.DGlutNk6.js → 6.C7e6zQyP.js} +1 -1
- package/build/client/_app/immutable/nodes/6.C7e6zQyP.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.C7e6zQyP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.BXAYsEHF.js → 7.1ygvNTnO.js} +1 -1
- package/build/client/_app/immutable/nodes/7.1ygvNTnO.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.1ygvNTnO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.D-SxCq24.js → 8.CBVmgOk0.js} +1 -1
- package/build/client/_app/immutable/nodes/8.CBVmgOk0.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.CBVmgOk0.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.Bek2YR4U.js → 9.B-_ZFZhj.js} +1 -1
- package/build/client/_app/immutable/nodes/9.B-_ZFZhj.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.B-_ZFZhj.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-ByOnI9Md.js → 0-BHU07xt9.js} +2 -2
- package/build/server/chunks/{0-ByOnI9Md.js.map → 0-BHU07xt9.js.map} +1 -1
- package/build/server/chunks/{1-DcJwehLu.js → 1-OVOd8GUH.js} +2 -2
- package/build/server/chunks/{1-DcJwehLu.js.map → 1-OVOd8GUH.js.map} +1 -1
- package/build/server/chunks/{10-B5bdfDfK.js → 10-CRHtvb_u.js} +2 -2
- package/build/server/chunks/{10-B5bdfDfK.js.map → 10-CRHtvb_u.js.map} +1 -1
- package/build/server/chunks/{11-DOJ9j7KC.js → 11-CdSex9j1.js} +2 -2
- package/build/server/chunks/{11-DOJ9j7KC.js.map → 11-CdSex9j1.js.map} +1 -1
- package/build/server/chunks/{2-BY0LGSMl.js → 2-bb78aIZ6.js} +2 -2
- package/build/server/chunks/{2-BY0LGSMl.js.map → 2-bb78aIZ6.js.map} +1 -1
- package/build/server/chunks/{3-DEa6RtDT.js → 3-CsTC6Lrn.js} +2 -2
- package/build/server/chunks/{3-DEa6RtDT.js.map → 3-CsTC6Lrn.js.map} +1 -1
- package/build/server/chunks/{4-w2W_T8ax.js → 4-DTTu8_Gr.js} +4 -4
- package/build/server/chunks/{4-w2W_T8ax.js.map → 4-DTTu8_Gr.js.map} +1 -1
- package/build/server/chunks/{6-DoTu6ygH.js → 6-BUgQGB_4.js} +2 -2
- package/build/server/chunks/{6-DoTu6ygH.js.map → 6-BUgQGB_4.js.map} +1 -1
- package/build/server/chunks/{7-CYQ9V6d4.js → 7-CsQZnkcG.js} +2 -2
- package/build/server/chunks/{7-CYQ9V6d4.js.map → 7-CsQZnkcG.js.map} +1 -1
- package/build/server/chunks/{8-DMwmCD5X.js → 8-DcIuPdyW.js} +2 -2
- package/build/server/chunks/{8-DMwmCD5X.js.map → 8-DcIuPdyW.js.map} +1 -1
- package/build/server/chunks/{9-CNmPa2Jo.js → 9-D8bkM1uj.js} +2 -2
- package/build/server/chunks/{9-CNmPa2Jo.js.map → 9-D8bkM1uj.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-DDE2nChH.js → _page.svelte-BBbaKwNz.js} +101 -27
- package/build/server/chunks/_page.svelte-BBbaKwNz.js.map +1 -0
- package/build/server/chunks/{_server.ts-DfscXcFe.js → _server.ts-B7BLxK5u.js} +233 -186
- package/build/server/chunks/_server.ts-B7BLxK5u.js.map +1 -0
- package/build/server/chunks/{_server.ts-EJVmhLtg.js → _server.ts-BSS8cO80.js} +15 -3
- package/build/server/chunks/_server.ts-BSS8cO80.js.map +1 -0
- package/build/server/chunks/_server.ts-DNTxPoxO.js +115 -0
- package/build/server/chunks/_server.ts-DNTxPoxO.js.map +1 -0
- package/build/server/chunks/{_server.ts-D9_hkPQ6.js → _server.ts-DUb7fbuW.js} +17 -3
- package/build/server/chunks/_server.ts-DUb7fbuW.js.map +1 -0
- package/build/server/chunks/{_server.ts-v7TaT83B.js → _server.ts-FdKi8RwL.js} +7 -3
- package/build/server/chunks/_server.ts-FdKi8RwL.js.map +1 -0
- package/build/server/chunks/device-format-DTgEz4Yr.js +29 -0
- package/build/server/chunks/device-format-DTgEz4Yr.js.map +1 -0
- package/build/server/chunks/device-token-store-Ct7aTeR8.js +259 -0
- package/build/server/chunks/device-token-store-Ct7aTeR8.js.map +1 -0
- package/build/server/chunks/{library-apns-D8RPINlv.js → library-apns-DMlL1BAg.js} +158 -26
- package/build/server/chunks/library-apns-DMlL1BAg.js.map +1 -0
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +17 -17
- package/build/server/manifest.js.map +1 -1
- package/package.json +2 -2
- package/server.ts +15 -1
- package/src/app.d.ts +4 -0
- package/src/lib/modules/server/apn/apns-classify.ts +103 -0
- package/src/lib/modules/server/apn/library-apns.ts +151 -35
- package/src/lib/modules/server/apn/notify-fanout.ts +40 -0
- package/src/lib/modules/server/fcm/fcm-classify.ts +61 -0
- package/src/lib/modules/server/fcm/fcm-service.ts +128 -29
- package/src/lib/modules/server/push/device-format.ts +42 -0
- package/src/lib/modules/server/push/device-token-store.ts +354 -0
- package/src/lib/types/apn.ts +4 -0
- package/src/lib/types/device.ts +156 -0
- package/src/lib/types/index.ts +1 -0
- package/src/routes/api/debug/+server.ts +13 -1
- package/src/routes/api/device-token/+server.ts +122 -37
- package/src/routes/api/health/+server.ts +16 -2
- package/src/routes/api/notify/+server.ts +175 -168
- package/src/routes/api/qr-config/+server.ts +9 -2
- package/src/routes/config/+page.svelte +182 -44
- package/build/client/_app/immutable/assets/4.D4EDSN4H.css.br +0 -0
- package/build/client/_app/immutable/assets/4.D4EDSN4H.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BBuzUXZ9.js.br +0 -0
- package/build/client/_app/immutable/chunks/BBuzUXZ9.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BPIV28L3.js +0 -3
- package/build/client/_app/immutable/chunks/BPIV28L3.js.br +0 -0
- package/build/client/_app/immutable/chunks/BPIV28L3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dqmsaccg.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dqmsaccg.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.Bu4nq_bk.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Bu4nq_bk.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CbpzR6Gp.js +0 -1
- package/build/client/_app/immutable/entry/start.CbpzR6Gp.js.br +0 -2
- package/build/client/_app/immutable/entry/start.CbpzR6Gp.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.CnSSjKHX.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.CnSSjKHX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.D_5wZINa.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.D_5wZINa.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.C_B2eMms.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.C_B2eMms.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.DUKnn5ja.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.DUKnn5ja.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.C37CZKYv.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.C37CZKYv.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.C48G514u.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.C48G514u.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.C9fv_m2R.js +0 -16
- package/build/client/_app/immutable/nodes/4.C9fv_m2R.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.C9fv_m2R.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.DGlutNk6.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.DGlutNk6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.BXAYsEHF.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.BXAYsEHF.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.D-SxCq24.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.D-SxCq24.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.Bek2YR4U.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.Bek2YR4U.js.gz +0 -0
- package/build/server/chunks/_page.svelte-DDE2nChH.js.map +0 -1
- package/build/server/chunks/_server.ts-D2RS8TFd.js +0 -72
- package/build/server/chunks/_server.ts-D2RS8TFd.js.map +0 -1
- package/build/server/chunks/_server.ts-D9_hkPQ6.js.map +0 -1
- package/build/server/chunks/_server.ts-DfscXcFe.js.map +0 -1
- package/build/server/chunks/_server.ts-EJVmhLtg.js.map +0 -1
- package/build/server/chunks/_server.ts-v7TaT83B.js.map +0 -1
- package/build/server/chunks/library-apns-D8RPINlv.js.map +0 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { b as private_env } from './shared-server-DaWdgxVh.js';
|
|
2
|
+
import { v as validateAuth } from './auth-DuunT7Cg.js';
|
|
3
|
+
import { t as toDeviceListItem } from './device-format-DTgEz4Yr.js';
|
|
4
|
+
import { d as deviceTokenStore, M as MAX_TOKEN_LENGTH, a as MAX_DEVICE_ID_LENGTH, b as MAX_NAME_LENGTH, c as MAX_BUNDLE_ID_LENGTH } from './device-token-store-Ct7aTeR8.js';
|
|
5
|
+
import { j as json } from './index-lhTMmBNn.js';
|
|
6
|
+
import 'crypto';
|
|
7
|
+
import 'better-sqlite3';
|
|
8
|
+
import 'fs';
|
|
9
|
+
import 'path';
|
|
10
|
+
import './shooter-home-4f_HkdGI.js';
|
|
11
|
+
import 'os';
|
|
12
|
+
|
|
13
|
+
function resolveAppEnv(requested) {
|
|
14
|
+
if (requested === "production" || requested === "sandbox") {
|
|
15
|
+
return requested;
|
|
16
|
+
}
|
|
17
|
+
return private_env.APNS_PRODUCTION === "true" ? "production" : "sandbox";
|
|
18
|
+
}
|
|
19
|
+
const GET = ({ request }) => {
|
|
20
|
+
const authError = validateAuth(request);
|
|
21
|
+
if (authError) {
|
|
22
|
+
return authError;
|
|
23
|
+
}
|
|
24
|
+
const ios = deviceTokenStore.listActive("ios");
|
|
25
|
+
const android = deviceTokenStore.listActive("android");
|
|
26
|
+
const devices = [...ios, ...android].map(toDeviceListItem);
|
|
27
|
+
return json({
|
|
28
|
+
counts: { android: android.length, ios: ios.length, total: devices.length },
|
|
29
|
+
devices
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const DELETE = async ({ request }) => {
|
|
33
|
+
const authError = validateAuth(request);
|
|
34
|
+
if (authError) {
|
|
35
|
+
return authError;
|
|
36
|
+
}
|
|
37
|
+
let body;
|
|
38
|
+
try {
|
|
39
|
+
body = await request.json();
|
|
40
|
+
} catch {
|
|
41
|
+
return json({ error: "Invalid JSON body" }, { status: 400 });
|
|
42
|
+
}
|
|
43
|
+
const id = body && typeof body === "object" && !Array.isArray(body) ? body.id : void 0;
|
|
44
|
+
if (typeof id !== "string" || id.trim().length === 0) {
|
|
45
|
+
return json({ error: "Missing required field: id" }, { status: 400 });
|
|
46
|
+
}
|
|
47
|
+
const removed = deviceTokenStore.deleteById(id.trim());
|
|
48
|
+
if (removed === 0) {
|
|
49
|
+
return json({ error: "Device not found or already removed", id: id.trim() }, { status: 404 });
|
|
50
|
+
}
|
|
51
|
+
return json({ removed, success: true });
|
|
52
|
+
};
|
|
53
|
+
const POST = async ({ request }) => {
|
|
54
|
+
const authError = validateAuth(request);
|
|
55
|
+
if (authError) {
|
|
56
|
+
return authError;
|
|
57
|
+
}
|
|
58
|
+
let body;
|
|
59
|
+
try {
|
|
60
|
+
const parsed = await request.json();
|
|
61
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
62
|
+
return json({ error: "Invalid JSON body: expected an object" }, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
body = parsed;
|
|
65
|
+
} catch {
|
|
66
|
+
return json({ error: "Invalid JSON body" }, { status: 400 });
|
|
67
|
+
}
|
|
68
|
+
const platform = body.platform;
|
|
69
|
+
if (!platform || platform !== "ios" && platform !== "android") {
|
|
70
|
+
return json(
|
|
71
|
+
{ error: 'Missing or invalid platform (must be "ios" or "android")' },
|
|
72
|
+
{ status: 400 }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const rawToken = body.deviceToken || body.token;
|
|
76
|
+
if (!rawToken || typeof rawToken !== "string" || rawToken.trim().length === 0) {
|
|
77
|
+
return json({ error: "Missing device token (deviceToken or token)" }, { status: 400 });
|
|
78
|
+
}
|
|
79
|
+
const token = rawToken.trim();
|
|
80
|
+
const deviceId = typeof body.deviceId === "string" ? body.deviceId.trim() || null : null;
|
|
81
|
+
const friendlyName = typeof body.deviceName === "string" ? body.deviceName.trim() || null : null;
|
|
82
|
+
const bundleId = typeof body.bundleId === "string" ? body.bundleId.trim() || null : null;
|
|
83
|
+
const tooLong = token.length > MAX_TOKEN_LENGTH ? "token" : deviceId && deviceId.length > MAX_DEVICE_ID_LENGTH ? "deviceId" : friendlyName && friendlyName.length > MAX_NAME_LENGTH ? "deviceName" : bundleId && bundleId.length > MAX_BUNDLE_ID_LENGTH ? "bundleId" : null;
|
|
84
|
+
if (tooLong) {
|
|
85
|
+
return json({ error: `Field "${tooLong}" exceeds maximum allowed length` }, { status: 400 });
|
|
86
|
+
}
|
|
87
|
+
const appEnv = resolveAppEnv(body.appEnv);
|
|
88
|
+
const record = deviceTokenStore.upsert({
|
|
89
|
+
appEnv,
|
|
90
|
+
bundleId,
|
|
91
|
+
deviceId,
|
|
92
|
+
friendlyName,
|
|
93
|
+
platform,
|
|
94
|
+
token
|
|
95
|
+
});
|
|
96
|
+
if (record.deviceId !== deviceId) {
|
|
97
|
+
return json(
|
|
98
|
+
{ error: "Token is already registered to a different active device" },
|
|
99
|
+
{ status: 409 }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
console.log(
|
|
103
|
+
`[device-token] Registered ${platform} device ${record.id} (token len ${token.length})`
|
|
104
|
+
);
|
|
105
|
+
return json({
|
|
106
|
+
deviceId: record.deviceId,
|
|
107
|
+
id: record.id,
|
|
108
|
+
platform,
|
|
109
|
+
success: true,
|
|
110
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export { DELETE, GET, POST };
|
|
115
|
+
//# sourceMappingURL=_server.ts-DNTxPoxO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_server.ts-DNTxPoxO.js","sources":["../../../.svelte-kit/adapter-node/entries/endpoints/api/device-token/_server.ts.js"],"sourcesContent":["import { b as private_env } from \"../../../../chunks/shared-server.js\";\nimport { v as validateAuth } from \"../../../../chunks/auth.js\";\nimport { t as toDeviceListItem } from \"../../../../chunks/device-format.js\";\nimport { d as deviceTokenStore, M as MAX_TOKEN_LENGTH, a as MAX_DEVICE_ID_LENGTH, b as MAX_NAME_LENGTH, c as MAX_BUNDLE_ID_LENGTH } from \"../../../../chunks/device-token-store.js\";\nimport { json } from \"@sveltejs/kit\";\nfunction resolveAppEnv(requested) {\n if (requested === \"production\" || requested === \"sandbox\") {\n return requested;\n }\n return private_env.APNS_PRODUCTION === \"true\" ? \"production\" : \"sandbox\";\n}\nconst GET = ({ request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n const ios = deviceTokenStore.listActive(\"ios\");\n const android = deviceTokenStore.listActive(\"android\");\n const devices = [...ios, ...android].map(toDeviceListItem);\n return json({\n counts: { android: android.length, ios: ios.length, total: devices.length },\n devices\n });\n};\nconst DELETE = async ({ request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n let body;\n try {\n body = await request.json();\n } catch {\n return json({ error: \"Invalid JSON body\" }, { status: 400 });\n }\n const id = body && typeof body === \"object\" && !Array.isArray(body) ? body.id : void 0;\n if (typeof id !== \"string\" || id.trim().length === 0) {\n return json({ error: \"Missing required field: id\" }, { status: 400 });\n }\n const removed = deviceTokenStore.deleteById(id.trim());\n if (removed === 0) {\n return json({ error: \"Device not found or already removed\", id: id.trim() }, { status: 404 });\n }\n return json({ removed, success: true });\n};\nconst POST = async ({ request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n let body;\n try {\n const parsed = await request.json();\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return json({ error: \"Invalid JSON body: expected an object\" }, { status: 400 });\n }\n body = parsed;\n } catch {\n return json({ error: \"Invalid JSON body\" }, { status: 400 });\n }\n const platform = body.platform;\n if (!platform || platform !== \"ios\" && platform !== \"android\") {\n return json(\n { error: 'Missing or invalid platform (must be \"ios\" or \"android\")' },\n { status: 400 }\n );\n }\n const rawToken = body.deviceToken || body.token;\n if (!rawToken || typeof rawToken !== \"string\" || rawToken.trim().length === 0) {\n return json({ error: \"Missing device token (deviceToken or token)\" }, { status: 400 });\n }\n const token = rawToken.trim();\n const deviceId = typeof body.deviceId === \"string\" ? body.deviceId.trim() || null : null;\n const friendlyName = typeof body.deviceName === \"string\" ? body.deviceName.trim() || null : null;\n const bundleId = typeof body.bundleId === \"string\" ? body.bundleId.trim() || null : null;\n const tooLong = token.length > MAX_TOKEN_LENGTH ? \"token\" : deviceId && deviceId.length > MAX_DEVICE_ID_LENGTH ? \"deviceId\" : friendlyName && friendlyName.length > MAX_NAME_LENGTH ? \"deviceName\" : bundleId && bundleId.length > MAX_BUNDLE_ID_LENGTH ? \"bundleId\" : null;\n if (tooLong) {\n return json({ error: `Field \"${tooLong}\" exceeds maximum allowed length` }, { status: 400 });\n }\n const appEnv = resolveAppEnv(body.appEnv);\n const record = deviceTokenStore.upsert({\n appEnv,\n bundleId,\n deviceId,\n friendlyName,\n platform,\n token\n });\n if (record.deviceId !== deviceId) {\n return json(\n { error: \"Token is already registered to a different active device\" },\n { status: 409 }\n );\n }\n console.log(\n `[device-token] Registered ${platform} device ${record.id} (token len ${token.length})`\n );\n return json({\n deviceId: record.deviceId,\n id: record.id,\n platform,\n success: true,\n timestamp: (/* @__PURE__ */ new Date()).toISOString()\n });\n};\nexport {\n DELETE,\n GET,\n POST\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { b as private_env } from './shared-server-DaWdgxVh.js';
|
|
2
2
|
import { v as validateAuth } from './auth-DuunT7Cg.js';
|
|
3
|
+
import { d as deviceTokenStore } from './device-token-store-Ct7aTeR8.js';
|
|
3
4
|
import { g as getProviderAvailability } from './providers-DtstoHQ0.js';
|
|
4
5
|
import { j as json } from './index-lhTMmBNn.js';
|
|
5
6
|
import { readFileSync } from 'fs';
|
|
6
7
|
import { join } from 'path';
|
|
7
8
|
import 'crypto';
|
|
9
|
+
import 'better-sqlite3';
|
|
10
|
+
import './shooter-home-4f_HkdGI.js';
|
|
11
|
+
import 'os';
|
|
8
12
|
|
|
9
13
|
const PKG_VERSION = (() => {
|
|
10
14
|
const root = process.env.SHOOTER_PKG_ROOT || process.cwd();
|
|
@@ -26,11 +30,16 @@ const GET = ({ request, url }) => {
|
|
|
26
30
|
const hasProjectId = !!private_env.FCM_PROJECT_ID?.trim();
|
|
27
31
|
const hasClientEmail = !!private_env.FCM_CLIENT_EMAIL?.trim();
|
|
28
32
|
const hasPrivateKey = !!private_env.FCM_PRIVATE_KEY?.trim();
|
|
33
|
+
const iosDeviceCount = deviceTokenStore.listActive("ios").length;
|
|
34
|
+
const androidDeviceCount = deviceTokenStore.listActive("android").length;
|
|
35
|
+
const registeredDeviceCount = iosDeviceCount + androidDeviceCount;
|
|
29
36
|
const checks = {
|
|
30
37
|
hasApiKey: !!private_env.API_KEY?.trim(),
|
|
31
38
|
hasAPNsConfig: !!(private_env.APNS_KEY_ID?.trim() && private_env.APNS_TEAM_ID?.trim() && private_env.APNS_KEY?.trim()),
|
|
32
39
|
hasBundleId: !!private_env.APNS_BUNDLE_ID?.trim(),
|
|
33
|
-
|
|
40
|
+
// Backward-compat alias: true if any device is registered OR the legacy
|
|
41
|
+
// single-token env var is still set.
|
|
42
|
+
hasDeviceToken: registeredDeviceCount > 0 || !!private_env.DEVICE_TOKEN?.trim() || !!private_env.ANDROID_DEVICE_TOKEN?.trim(),
|
|
34
43
|
hasFCMConfig: hasProjectId && hasClientEmail && hasPrivateKey
|
|
35
44
|
};
|
|
36
45
|
const fcm = {
|
|
@@ -51,7 +60,7 @@ const GET = ({ request, url }) => {
|
|
|
51
60
|
warnings.push("APNs not configured — iOS push notifications disabled");
|
|
52
61
|
}
|
|
53
62
|
if (!checks.hasDeviceToken) {
|
|
54
|
-
warnings.push("No
|
|
63
|
+
warnings.push("No devices registered — push notifications have no target");
|
|
55
64
|
}
|
|
56
65
|
if (!checks.hasFCMConfig) {
|
|
57
66
|
warnings.push("FCM not configured — Android push notifications disabled");
|
|
@@ -71,6 +80,11 @@ const GET = ({ request, url }) => {
|
|
|
71
80
|
},
|
|
72
81
|
checks,
|
|
73
82
|
configuration,
|
|
83
|
+
devices: {
|
|
84
|
+
android: androidDeviceCount,
|
|
85
|
+
ios: iosDeviceCount,
|
|
86
|
+
total: registeredDeviceCount
|
|
87
|
+
},
|
|
74
88
|
environment: private_env.NODE_ENV || "development",
|
|
75
89
|
status: "healthy",
|
|
76
90
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -90,4 +104,4 @@ const GET = ({ request, url }) => {
|
|
|
90
104
|
};
|
|
91
105
|
|
|
92
106
|
export { GET };
|
|
93
|
-
//# sourceMappingURL=_server.ts-
|
|
107
|
+
//# sourceMappingURL=_server.ts-DUb7fbuW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_server.ts-DUb7fbuW.js","sources":["../../../.svelte-kit/adapter-node/entries/endpoints/api/health/_server.ts.js"],"sourcesContent":["import { b as private_env } from \"../../../../chunks/shared-server.js\";\nimport { v as validateAuth } from \"../../../../chunks/auth.js\";\nimport { d as deviceTokenStore } from \"../../../../chunks/device-token-store.js\";\nimport { g as getProviderAvailability } from \"../../../../chunks/providers.js\";\nimport { json } from \"@sveltejs/kit\";\nimport { readFileSync } from \"fs\";\nimport { join } from \"path\";\nconst PKG_VERSION = (() => {\n const root = process.env.SHOOTER_PKG_ROOT || process.cwd();\n try {\n const pkg = JSON.parse(readFileSync(join(root, \"package.json\"), \"utf-8\"));\n return pkg.version || \"unknown\";\n } catch {\n return \"unknown\";\n }\n})();\nconst GET = ({ request, url }) => {\n const wantsDetails = url.searchParams.get(\"details\") === \"true\";\n if (wantsDetails) {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n }\n const hasProjectId = !!private_env.FCM_PROJECT_ID?.trim();\n const hasClientEmail = !!private_env.FCM_CLIENT_EMAIL?.trim();\n const hasPrivateKey = !!private_env.FCM_PRIVATE_KEY?.trim();\n const iosDeviceCount = deviceTokenStore.listActive(\"ios\").length;\n const androidDeviceCount = deviceTokenStore.listActive(\"android\").length;\n const registeredDeviceCount = iosDeviceCount + androidDeviceCount;\n const checks = {\n hasApiKey: !!private_env.API_KEY?.trim(),\n hasAPNsConfig: !!(private_env.APNS_KEY_ID?.trim() && private_env.APNS_TEAM_ID?.trim() && private_env.APNS_KEY?.trim()),\n hasBundleId: !!private_env.APNS_BUNDLE_ID?.trim(),\n // Backward-compat alias: true if any device is registered OR the legacy\n // single-token env var is still set.\n hasDeviceToken: registeredDeviceCount > 0 || !!private_env.DEVICE_TOKEN?.trim() || !!private_env.ANDROID_DEVICE_TOKEN?.trim(),\n hasFCMConfig: hasProjectId && hasClientEmail && hasPrivateKey\n };\n const fcm = {\n configured: hasProjectId && hasClientEmail && hasPrivateKey,\n hasClientEmail,\n hasPrivateKey,\n hasProjectId\n };\n const configuration = {\n apnsKeyId: private_env.APNS_KEY_ID ? `${private_env.APNS_KEY_ID.substring(0, 4)}...` : \"\",\n bundleId: private_env.APNS_BUNDLE_ID || \"\",\n deviceTokenLength: private_env.DEVICE_TOKEN ? private_env.DEVICE_TOKEN.length : 0,\n fcm,\n production: private_env.APNS_PRODUCTION === \"true\"\n };\n const warnings = [];\n if (!checks.hasAPNsConfig || !checks.hasBundleId) {\n warnings.push(\"APNs not configured — iOS push notifications disabled\");\n }\n if (!checks.hasDeviceToken) {\n warnings.push(\"No devices registered — push notifications have no target\");\n }\n if (!checks.hasFCMConfig) {\n warnings.push(\"FCM not configured — Android push notifications disabled\");\n }\n const aiProviders = getProviderAvailability(private_env);\n const hasAnyAiProvider = Object.values(aiProviders).some(Boolean);\n if (!hasAnyAiProvider) {\n warnings.push(\n 'No AI provider configured — AI summaries disabled. Run \"shooter setup\" to configure.'\n );\n }\n const health = {\n ai: {\n activeProvider: private_env.NEUROLINK_PROVIDER ?? \"auto\",\n hasAnyProvider: hasAnyAiProvider,\n providers: aiProviders\n },\n checks,\n configuration,\n devices: {\n android: androidDeviceCount,\n ios: iosDeviceCount,\n total: registeredDeviceCount\n },\n environment: private_env.NODE_ENV || \"development\",\n status: \"healthy\",\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n version: PKG_VERSION,\n warnings\n };\n if (!wantsDetails) {\n const publicWarnings = health.warnings.filter((w) => !w.includes(\"AI provider\"));\n return json({\n status: health.status,\n timestamp: health.timestamp,\n version: health.version,\n warnings: publicWarnings\n });\n }\n return json(health);\n};\nexport {\n GET\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -14,8 +14,12 @@ const GET = async ({ request, url }) => {
|
|
|
14
14
|
if (!apiKey) {
|
|
15
15
|
return json({ error: "API_KEY not configured on server" }, { status: 500 });
|
|
16
16
|
}
|
|
17
|
-
const serverUrl = private_env.ORIGIN?.trim() || url.origin;
|
|
18
|
-
const configPayload = JSON.stringify({
|
|
17
|
+
const serverUrl = (private_env.ORIGIN?.trim() || url.origin).replace(/\/+$/, "");
|
|
18
|
+
const configPayload = JSON.stringify({
|
|
19
|
+
apiKey,
|
|
20
|
+
registerUrl: `${serverUrl}/api/device-token`,
|
|
21
|
+
serverUrl
|
|
22
|
+
});
|
|
19
23
|
try {
|
|
20
24
|
const dataUrl = await QRCode.toDataURL(configPayload, {
|
|
21
25
|
color: {
|
|
@@ -35,4 +39,4 @@ const GET = async ({ request, url }) => {
|
|
|
35
39
|
};
|
|
36
40
|
|
|
37
41
|
export { GET };
|
|
38
|
-
//# sourceMappingURL=_server.ts-
|
|
42
|
+
//# sourceMappingURL=_server.ts-FdKi8RwL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_server.ts-FdKi8RwL.js","sources":["../../../.svelte-kit/adapter-node/entries/endpoints/api/qr-config/_server.ts.js"],"sourcesContent":["import { b as private_env } from \"../../../../chunks/shared-server.js\";\nimport { v as validateAuth } from \"../../../../chunks/auth.js\";\nimport { t as toErrorMessage } from \"../../../../chunks/error.js\";\nimport { json } from \"@sveltejs/kit\";\nimport QRCode from \"qrcode\";\nconst GET = async ({ request, url }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n const apiKey = private_env.API_KEY?.trim();\n if (!apiKey) {\n return json({ error: \"API_KEY not configured on server\" }, { status: 500 });\n }\n const serverUrl = (private_env.ORIGIN?.trim() || url.origin).replace(/\\/+$/, \"\");\n const configPayload = JSON.stringify({\n apiKey,\n registerUrl: `${serverUrl}/api/device-token`,\n serverUrl\n });\n try {\n const dataUrl = await QRCode.toDataURL(configPayload, {\n color: {\n dark: \"#ededed\",\n light: \"#0a0a0a\"\n },\n errorCorrectionLevel: \"M\",\n margin: 2,\n type: \"image/png\",\n width: 280\n });\n return json({ dataUrl, serverUrl });\n } catch (error) {\n console.error(\"[qr-config] QR generation failed:\", toErrorMessage(error));\n return json({ error: \"Failed to generate QR code\" }, { status: 500 });\n }\n};\nexport {\n GET\n};\n"],"names":[],"mappings":";;;;;;;AAKK,MAAC,GAAG,GAAG,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK;AACxC,EAAE,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;AACzC,EAAE,IAAI,SAAS,EAAE;AACjB,IAAI,OAAO,SAAS;AACpB,EAAE;AACF,EAAE,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE;AAC5C,EAAE,IAAI,CAAC,MAAM,EAAE;AACf,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,kCAAkC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AAC/E,EAAE;AACF,EAAE,MAAM,SAAS,GAAG,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;AAClF,EAAE,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC;AACvC,IAAI,MAAM;AACV,IAAI,WAAW,EAAE,CAAC,EAAE,SAAS,CAAC,iBAAiB,CAAC;AAChD,IAAI;AACJ,GAAG,CAAC;AACJ,EAAE,IAAI;AACN,IAAI,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,aAAa,EAAE;AAC1D,MAAM,KAAK,EAAE;AACb,QAAQ,IAAI,EAAE,SAAS;AACvB,QAAQ,KAAK,EAAE;AACf,OAAO;AACP,MAAM,oBAAoB,EAAE,GAAG;AAC/B,MAAM,MAAM,EAAE,CAAC;AACf,MAAM,IAAI,EAAE,WAAW;AACvB,MAAM,KAAK,EAAE;AACb,KAAK,CAAC;AACN,IAAI,OAAO,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACvC,EAAE,CAAC,CAAC,OAAO,KAAK,EAAE;AAClB,IAAI,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;AAC7E,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,4BAA4B,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AACzE,EAAE;AACF;;;;"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function maskToken(token) {
|
|
2
|
+
if (!token) {
|
|
3
|
+
return "";
|
|
4
|
+
}
|
|
5
|
+
if (token.length < 8) {
|
|
6
|
+
return "••••";
|
|
7
|
+
}
|
|
8
|
+
if (token.length <= 10) {
|
|
9
|
+
return `${token.slice(0, 2)}…${token.slice(-2)}`;
|
|
10
|
+
}
|
|
11
|
+
return `${token.slice(0, 6)}…${token.slice(-4)}`;
|
|
12
|
+
}
|
|
13
|
+
function toDeviceListItem(record) {
|
|
14
|
+
return {
|
|
15
|
+
appEnv: record.appEnv,
|
|
16
|
+
deviceId: record.deviceId,
|
|
17
|
+
failureCount: record.failureCount,
|
|
18
|
+
friendlyName: record.friendlyName,
|
|
19
|
+
id: record.id,
|
|
20
|
+
isActive: record.isActive,
|
|
21
|
+
lastSeenAt: record.lastSeenAt,
|
|
22
|
+
platform: record.platform,
|
|
23
|
+
registeredAt: record.registeredAt,
|
|
24
|
+
tokenMasked: maskToken(record.token)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { maskToken as m, toDeviceListItem as t };
|
|
29
|
+
//# sourceMappingURL=device-format-DTgEz4Yr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-format-DTgEz4Yr.js","sources":["../../../.svelte-kit/adapter-node/chunks/device-format.js"],"sourcesContent":["function maskToken(token) {\n if (!token) {\n return \"\";\n }\n if (token.length < 8) {\n return \"••••\";\n }\n if (token.length <= 10) {\n return `${token.slice(0, 2)}…${token.slice(-2)}`;\n }\n return `${token.slice(0, 6)}…${token.slice(-4)}`;\n}\nfunction toDeviceListItem(record) {\n return {\n appEnv: record.appEnv,\n deviceId: record.deviceId,\n failureCount: record.failureCount,\n friendlyName: record.friendlyName,\n id: record.id,\n isActive: record.isActive,\n lastSeenAt: record.lastSeenAt,\n platform: record.platform,\n registeredAt: record.registeredAt,\n tokenMasked: maskToken(record.token)\n };\n}\nexport {\n maskToken as m,\n toDeviceListItem as t\n};\n"],"names":[],"mappings":"AAAA,SAAS,SAAS,CAAC,KAAK,EAAE;AAC1B,EAAE,IAAI,CAAC,KAAK,EAAE;AACd,IAAI,OAAO,EAAE;AACb,EAAE;AACF,EAAE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;AACxB,IAAI,OAAO,MAAM;AACjB,EAAE;AACF,EAAE,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE;AAC1B,IAAI,OAAO,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AACpD,EAAE;AACF,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AAClD;AACA,SAAS,gBAAgB,CAAC,MAAM,EAAE;AAClC,EAAE,OAAO;AACT,IAAI,MAAM,EAAE,MAAM,CAAC,MAAM;AACzB,IAAI,QAAQ,EAAE,MAAM,CAAC,QAAQ;AAC7B,IAAI,YAAY,EAAE,MAAM,CAAC,YAAY;AACrC,IAAI,YAAY,EAAE,MAAM,CAAC,YAAY;AACrC,IAAI,EAAE,EAAE,MAAM,CAAC,EAAE;AACjB,IAAI,QAAQ,EAAE,MAAM,CAAC,QAAQ;AAC7B,IAAI,UAAU,EAAE,MAAM,CAAC,UAAU;AACjC,IAAI,QAAQ,EAAE,MAAM,CAAC,QAAQ;AAC7B,IAAI,YAAY,EAAE,MAAM,CAAC,YAAY;AACrC,IAAI,WAAW,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK;AACvC,GAAG;AACH;;;;"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { s as shooterDataDir } from './shooter-home-4f_HkdGI.js';
|
|
6
|
+
|
|
7
|
+
function isDeviceRecord(v) {
|
|
8
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const r = v;
|
|
12
|
+
const isNullableString = (x) => x === null || typeof x === "string";
|
|
13
|
+
return typeof r.id === "string" && typeof r.token === "string" && (r.platform === "ios" || r.platform === "android") && (r.appEnv === "sandbox" || r.appEnv === "production") && typeof r.registeredAt === "string" && typeof r.lastSeenAt === "string" && typeof r.isActive === "boolean" && Number.isInteger(r.failureCount) && r.failureCount >= 0 && isNullableString(r.bundleId) && isNullableString(r.deviceId) && isNullableString(r.friendlyName);
|
|
14
|
+
}
|
|
15
|
+
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
16
|
+
const MAX_TOKEN_LENGTH = 512;
|
|
17
|
+
const MAX_DEVICE_ID_LENGTH = 256;
|
|
18
|
+
const MAX_NAME_LENGTH = 256;
|
|
19
|
+
const MAX_BUNDLE_ID_LENGTH = 256;
|
|
20
|
+
class DeviceTokenStore {
|
|
21
|
+
dataDir;
|
|
22
|
+
db;
|
|
23
|
+
constructor(dataDir = shooterDataDir()) {
|
|
24
|
+
this.dataDir = dataDir;
|
|
25
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
26
|
+
this.db = new Database(path.join(dataDir, "shooter.db"));
|
|
27
|
+
this.db.pragma("journal_mode = WAL");
|
|
28
|
+
this.db.exec(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS device_tokens (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
token TEXT NOT NULL,
|
|
32
|
+
platform TEXT NOT NULL CHECK(platform IN ('ios', 'android')),
|
|
33
|
+
app_env TEXT NOT NULL DEFAULT 'sandbox'
|
|
34
|
+
CHECK(app_env IN ('sandbox', 'production')),
|
|
35
|
+
device_id TEXT,
|
|
36
|
+
friendly_name TEXT,
|
|
37
|
+
bundle_id TEXT,
|
|
38
|
+
registered_at TEXT NOT NULL,
|
|
39
|
+
last_seen_at TEXT NOT NULL,
|
|
40
|
+
failure_count INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
42
|
+
UNIQUE(token),
|
|
43
|
+
UNIQUE(device_id, platform)
|
|
44
|
+
);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_device_tokens_active
|
|
46
|
+
ON device_tokens(platform, is_active);
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
close() {
|
|
50
|
+
this.db.close();
|
|
51
|
+
}
|
|
52
|
+
/** Soft-delete a device by id (sets is_active = 0; auditable until cleanup). */
|
|
53
|
+
deleteById(id) {
|
|
54
|
+
return this.db.prepare("UPDATE device_tokens SET is_active = 0 WHERE id = ?").run(id).changes;
|
|
55
|
+
}
|
|
56
|
+
getByToken(token) {
|
|
57
|
+
const row = this.db.prepare("SELECT * FROM device_tokens WHERE token = ?").get(token);
|
|
58
|
+
return row ? rowToRecord(row) : null;
|
|
59
|
+
}
|
|
60
|
+
/** All active tokens for a platform, most-recently-seen first. */
|
|
61
|
+
listActive(platform) {
|
|
62
|
+
const rows = this.db.prepare(
|
|
63
|
+
"SELECT * FROM device_tokens WHERE platform = ? AND is_active = 1 ORDER BY last_seen_at DESC"
|
|
64
|
+
).all(platform);
|
|
65
|
+
return rows.map(rowToRecord);
|
|
66
|
+
}
|
|
67
|
+
/** Active tokens for a platform filtered by APNs gateway env. */
|
|
68
|
+
listActiveForEnv(platform, appEnv) {
|
|
69
|
+
const rows = this.db.prepare(
|
|
70
|
+
`SELECT * FROM device_tokens
|
|
71
|
+
WHERE platform = ? AND app_env = ? AND is_active = 1
|
|
72
|
+
ORDER BY last_seen_at DESC`
|
|
73
|
+
).all(platform, appEnv);
|
|
74
|
+
return rows.map(rowToRecord);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Import tokens from the legacy device-tokens.json and the setup-wizard
|
|
78
|
+
* device-token-seeds.json, then rename the consumed files. Idempotent: the
|
|
79
|
+
* rename prevents re-import, and INSERT OR IGNORE prevents UNIQUE(token) dups.
|
|
80
|
+
*/
|
|
81
|
+
migrate(now = /* @__PURE__ */ new Date()) {
|
|
82
|
+
this.importFile(path.join(this.dataDir, "device-tokens.json"), ".migrated", now);
|
|
83
|
+
this.importFile(path.join(this.dataDir, "device-token-seeds.json"), ".processed", now);
|
|
84
|
+
}
|
|
85
|
+
/** Soft-delete a set of dead tokens (lazy pruning after a failed delivery). */
|
|
86
|
+
pruneByTokens(tokens) {
|
|
87
|
+
if (tokens.length === 0) {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
const placeholders = tokens.map(() => "?").join(", ");
|
|
91
|
+
return this.db.prepare(`UPDATE device_tokens SET is_active = 0 WHERE token IN (${placeholders})`).run(...tokens).changes;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Hard-delete inactive rows whose last_seen_at is older than 30 days.
|
|
95
|
+
* Active rows are NEVER deleted by age — a device that is still on but
|
|
96
|
+
* hasn't re-registered should keep receiving notifications.
|
|
97
|
+
*/
|
|
98
|
+
startupCleanup(now = /* @__PURE__ */ new Date()) {
|
|
99
|
+
const cutoff = new Date(now.getTime() - THIRTY_DAYS_MS).toISOString();
|
|
100
|
+
return this.db.prepare("DELETE FROM device_tokens WHERE is_active = 0 AND last_seen_at < ?").run(cutoff).changes;
|
|
101
|
+
}
|
|
102
|
+
/** Bump last_seen_at for tokens that just delivered successfully. */
|
|
103
|
+
touchLastSeen(tokens, now = /* @__PURE__ */ new Date()) {
|
|
104
|
+
if (tokens.length === 0) {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
const placeholders = tokens.map(() => "?").join(", ");
|
|
108
|
+
return this.db.prepare(`UPDATE device_tokens SET last_seen_at = ? WHERE token IN (${placeholders})`).run(now.toISOString(), ...tokens).changes;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Register or refresh a device. Three cases, resolved in one transaction:
|
|
112
|
+
* 1. Known device (deviceId matches an existing row) → rotate token in place.
|
|
113
|
+
* If the incoming token is currently held by a DIFFERENT row, APNs has
|
|
114
|
+
* recycled it to us, so that stale row is hard-deleted first — otherwise
|
|
115
|
+
* the rotation UPDATE would violate UNIQUE(token). (A soft-delete would
|
|
116
|
+
* NOT free the constraint: the token value stays in the inactive row.)
|
|
117
|
+
* 2. New legacy device (no deviceId) → deactivate prior null-device rows for
|
|
118
|
+
* the platform (can't tell them apart) then insert/refresh by token.
|
|
119
|
+
* 3. New device (deviceId not yet known) → insert; on token conflict, the
|
|
120
|
+
* ownership WHERE guard gates the WHOLE update: an unknown device cannot
|
|
121
|
+
* steal OR clobber the metadata of a token already owned by another active
|
|
122
|
+
* device (the conflict resolves to a no-op, leaving that row untouched).
|
|
123
|
+
*/
|
|
124
|
+
upsert(input, now = /* @__PURE__ */ new Date()) {
|
|
125
|
+
const ts = now.toISOString();
|
|
126
|
+
const appEnv = input.appEnv ?? (process.env.APNS_PRODUCTION === "true" ? "production" : "sandbox");
|
|
127
|
+
const deviceId = input.deviceId ?? null;
|
|
128
|
+
const friendlyName = input.friendlyName ?? null;
|
|
129
|
+
const bundleId = input.bundleId ?? null;
|
|
130
|
+
const { platform } = input;
|
|
131
|
+
const token = input.token.trim();
|
|
132
|
+
if (token.length === 0) {
|
|
133
|
+
throw new Error("DeviceTokenStore.upsert: token must be a non-empty string");
|
|
134
|
+
}
|
|
135
|
+
if (token.length > MAX_TOKEN_LENGTH) {
|
|
136
|
+
throw new Error(`DeviceTokenStore.upsert: token exceeds ${MAX_TOKEN_LENGTH} chars`);
|
|
137
|
+
}
|
|
138
|
+
if (deviceId !== null && deviceId.length > MAX_DEVICE_ID_LENGTH) {
|
|
139
|
+
throw new Error(`DeviceTokenStore.upsert: deviceId exceeds ${MAX_DEVICE_ID_LENGTH} chars`);
|
|
140
|
+
}
|
|
141
|
+
if (friendlyName !== null && friendlyName.length > MAX_NAME_LENGTH) {
|
|
142
|
+
throw new Error(`DeviceTokenStore.upsert: friendlyName exceeds ${MAX_NAME_LENGTH} chars`);
|
|
143
|
+
}
|
|
144
|
+
if (bundleId !== null && bundleId.length > MAX_BUNDLE_ID_LENGTH) {
|
|
145
|
+
throw new Error(`DeviceTokenStore.upsert: bundleId exceeds ${MAX_BUNDLE_ID_LENGTH} chars`);
|
|
146
|
+
}
|
|
147
|
+
this.db.transaction(() => {
|
|
148
|
+
if (deviceId !== null) {
|
|
149
|
+
const ownRow = this.db.prepare("SELECT id FROM device_tokens WHERE device_id = ? AND platform = ?").get(deviceId, platform);
|
|
150
|
+
if (ownRow) {
|
|
151
|
+
this.db.prepare("DELETE FROM device_tokens WHERE token = ? AND id != ?").run(token, ownRow.id);
|
|
152
|
+
this.db.prepare(
|
|
153
|
+
`UPDATE device_tokens SET
|
|
154
|
+
token = ?,
|
|
155
|
+
last_seen_at = ?,
|
|
156
|
+
app_env = ?,
|
|
157
|
+
failure_count = 0,
|
|
158
|
+
is_active = 1,
|
|
159
|
+
friendly_name = COALESCE(?, friendly_name),
|
|
160
|
+
bundle_id = COALESCE(?, bundle_id)
|
|
161
|
+
WHERE id = ?`
|
|
162
|
+
).run(token, ts, appEnv, friendlyName, bundleId, ownRow.id);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
this.db.prepare(
|
|
167
|
+
`UPDATE device_tokens SET is_active = 0
|
|
168
|
+
WHERE platform = ? AND device_id IS NULL AND token != ? AND is_active = 1`
|
|
169
|
+
).run(platform, token);
|
|
170
|
+
}
|
|
171
|
+
this.db.prepare(
|
|
172
|
+
`INSERT INTO device_tokens
|
|
173
|
+
(id, token, platform, app_env, device_id, friendly_name, bundle_id,
|
|
174
|
+
registered_at, last_seen_at, failure_count, is_active)
|
|
175
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1)
|
|
176
|
+
ON CONFLICT(token) DO UPDATE SET
|
|
177
|
+
last_seen_at = excluded.last_seen_at,
|
|
178
|
+
app_env = excluded.app_env,
|
|
179
|
+
failure_count = 0,
|
|
180
|
+
is_active = 1,
|
|
181
|
+
friendly_name = COALESCE(excluded.friendly_name, device_tokens.friendly_name),
|
|
182
|
+
bundle_id = COALESCE(excluded.bundle_id, device_tokens.bundle_id),
|
|
183
|
+
device_id = excluded.device_id
|
|
184
|
+
WHERE device_tokens.device_id IS NULL
|
|
185
|
+
OR device_tokens.device_id = excluded.device_id
|
|
186
|
+
OR device_tokens.is_active = 0`
|
|
187
|
+
).run(randomUUID(), token, platform, appEnv, deviceId, friendlyName, bundleId, ts, ts);
|
|
188
|
+
})();
|
|
189
|
+
const rec = this.getByToken(token);
|
|
190
|
+
if (!rec) {
|
|
191
|
+
throw new Error("DeviceTokenStore.upsert: row not found after write");
|
|
192
|
+
}
|
|
193
|
+
return rec;
|
|
194
|
+
}
|
|
195
|
+
importFile(filePath, renameSuffix, now) {
|
|
196
|
+
if (!fs.existsSync(filePath)) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
201
|
+
this.importTokenMap(raw, now);
|
|
202
|
+
fs.renameSync(filePath, filePath + renameSuffix);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.warn(`[device-token-store] failed to import ${filePath}:`, err);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
importTokenMap(raw, now) {
|
|
208
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const obj = raw;
|
|
212
|
+
const ts = now.toISOString();
|
|
213
|
+
const appEnv = process.env.APNS_PRODUCTION === "true" ? "production" : "sandbox";
|
|
214
|
+
const insert = this.db.prepare(
|
|
215
|
+
`INSERT OR IGNORE INTO device_tokens
|
|
216
|
+
(id, token, platform, app_env, device_id, friendly_name, bundle_id,
|
|
217
|
+
registered_at, last_seen_at, failure_count, is_active)
|
|
218
|
+
VALUES (?, ?, ?, ?, NULL, NULL, NULL, ?, ?, 0, 1)`
|
|
219
|
+
);
|
|
220
|
+
for (const platform of ["ios", "android"]) {
|
|
221
|
+
const value = obj[platform];
|
|
222
|
+
const tokens = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
223
|
+
for (const tok of tokens) {
|
|
224
|
+
if (typeof tok !== "string") {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const normalized = tok.trim();
|
|
228
|
+
if (normalized.length > 0) {
|
|
229
|
+
insert.run(randomUUID(), normalized, platform, appEnv, ts, ts);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function rowToRecord(row) {
|
|
236
|
+
const rec = {
|
|
237
|
+
appEnv: row.app_env,
|
|
238
|
+
bundleId: row.bundle_id ?? null,
|
|
239
|
+
deviceId: row.device_id ?? null,
|
|
240
|
+
failureCount: row.failure_count,
|
|
241
|
+
friendlyName: row.friendly_name ?? null,
|
|
242
|
+
id: row.id,
|
|
243
|
+
isActive: row.is_active === 1,
|
|
244
|
+
lastSeenAt: row.last_seen_at,
|
|
245
|
+
platform: row.platform,
|
|
246
|
+
registeredAt: row.registered_at,
|
|
247
|
+
token: row.token
|
|
248
|
+
};
|
|
249
|
+
if (!isDeviceRecord(rec)) {
|
|
250
|
+
throw new Error(`DeviceTokenStore: row failed DeviceRecord validation (id=${String(row.id)})`);
|
|
251
|
+
}
|
|
252
|
+
return rec;
|
|
253
|
+
}
|
|
254
|
+
const DTS_GLOBAL_KEY = "__shooter_device_token_store";
|
|
255
|
+
const deviceTokenStore = globalThis[DTS_GLOBAL_KEY] || new DeviceTokenStore();
|
|
256
|
+
globalThis[DTS_GLOBAL_KEY] = deviceTokenStore;
|
|
257
|
+
|
|
258
|
+
export { MAX_TOKEN_LENGTH as M, MAX_DEVICE_ID_LENGTH as a, MAX_NAME_LENGTH as b, MAX_BUNDLE_ID_LENGTH as c, deviceTokenStore as d };
|
|
259
|
+
//# sourceMappingURL=device-token-store-Ct7aTeR8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-token-store-Ct7aTeR8.js","sources":["../../../.svelte-kit/adapter-node/chunks/device-token-store.js"],"sourcesContent":["import Database from \"better-sqlite3\";\nimport { randomUUID } from \"crypto\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { s as shooterDataDir } from \"./shooter-home.js\";\nfunction isDeviceRecord(v) {\n if (!v || typeof v !== \"object\" || Array.isArray(v)) {\n return false;\n }\n const r = v;\n const isNullableString = (x) => x === null || typeof x === \"string\";\n return typeof r.id === \"string\" && typeof r.token === \"string\" && (r.platform === \"ios\" || r.platform === \"android\") && (r.appEnv === \"sandbox\" || r.appEnv === \"production\") && typeof r.registeredAt === \"string\" && typeof r.lastSeenAt === \"string\" && typeof r.isActive === \"boolean\" && Number.isInteger(r.failureCount) && r.failureCount >= 0 && isNullableString(r.bundleId) && isNullableString(r.deviceId) && isNullableString(r.friendlyName);\n}\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;\nconst MAX_TOKEN_LENGTH = 512;\nconst MAX_DEVICE_ID_LENGTH = 256;\nconst MAX_NAME_LENGTH = 256;\nconst MAX_BUNDLE_ID_LENGTH = 256;\nclass DeviceTokenStore {\n dataDir;\n db;\n constructor(dataDir = shooterDataDir()) {\n this.dataDir = dataDir;\n fs.mkdirSync(dataDir, { recursive: true });\n this.db = new Database(path.join(dataDir, \"shooter.db\"));\n this.db.pragma(\"journal_mode = WAL\");\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS device_tokens (\n id TEXT PRIMARY KEY,\n token TEXT NOT NULL,\n platform TEXT NOT NULL CHECK(platform IN ('ios', 'android')),\n app_env TEXT NOT NULL DEFAULT 'sandbox'\n CHECK(app_env IN ('sandbox', 'production')),\n device_id TEXT,\n friendly_name TEXT,\n bundle_id TEXT,\n registered_at TEXT NOT NULL,\n last_seen_at TEXT NOT NULL,\n failure_count INTEGER NOT NULL DEFAULT 0,\n is_active INTEGER NOT NULL DEFAULT 1,\n UNIQUE(token),\n UNIQUE(device_id, platform)\n );\n CREATE INDEX IF NOT EXISTS idx_device_tokens_active\n ON device_tokens(platform, is_active);\n `);\n }\n close() {\n this.db.close();\n }\n /** Soft-delete a device by id (sets is_active = 0; auditable until cleanup). */\n deleteById(id) {\n return this.db.prepare(\"UPDATE device_tokens SET is_active = 0 WHERE id = ?\").run(id).changes;\n }\n getByToken(token) {\n const row = this.db.prepare(\"SELECT * FROM device_tokens WHERE token = ?\").get(token);\n return row ? rowToRecord(row) : null;\n }\n /** All active tokens for a platform, most-recently-seen first. */\n listActive(platform) {\n const rows = this.db.prepare(\n \"SELECT * FROM device_tokens WHERE platform = ? AND is_active = 1 ORDER BY last_seen_at DESC\"\n ).all(platform);\n return rows.map(rowToRecord);\n }\n /** Active tokens for a platform filtered by APNs gateway env. */\n listActiveForEnv(platform, appEnv) {\n const rows = this.db.prepare(\n `SELECT * FROM device_tokens\n WHERE platform = ? AND app_env = ? AND is_active = 1\n ORDER BY last_seen_at DESC`\n ).all(platform, appEnv);\n return rows.map(rowToRecord);\n }\n /**\n * Import tokens from the legacy device-tokens.json and the setup-wizard\n * device-token-seeds.json, then rename the consumed files. Idempotent: the\n * rename prevents re-import, and INSERT OR IGNORE prevents UNIQUE(token) dups.\n */\n migrate(now = /* @__PURE__ */ new Date()) {\n this.importFile(path.join(this.dataDir, \"device-tokens.json\"), \".migrated\", now);\n this.importFile(path.join(this.dataDir, \"device-token-seeds.json\"), \".processed\", now);\n }\n /** Soft-delete a set of dead tokens (lazy pruning after a failed delivery). */\n pruneByTokens(tokens) {\n if (tokens.length === 0) {\n return 0;\n }\n const placeholders = tokens.map(() => \"?\").join(\", \");\n return this.db.prepare(`UPDATE device_tokens SET is_active = 0 WHERE token IN (${placeholders})`).run(...tokens).changes;\n }\n /**\n * Hard-delete inactive rows whose last_seen_at is older than 30 days.\n * Active rows are NEVER deleted by age — a device that is still on but\n * hasn't re-registered should keep receiving notifications.\n */\n startupCleanup(now = /* @__PURE__ */ new Date()) {\n const cutoff = new Date(now.getTime() - THIRTY_DAYS_MS).toISOString();\n return this.db.prepare(\"DELETE FROM device_tokens WHERE is_active = 0 AND last_seen_at < ?\").run(cutoff).changes;\n }\n /** Bump last_seen_at for tokens that just delivered successfully. */\n touchLastSeen(tokens, now = /* @__PURE__ */ new Date()) {\n if (tokens.length === 0) {\n return 0;\n }\n const placeholders = tokens.map(() => \"?\").join(\", \");\n return this.db.prepare(`UPDATE device_tokens SET last_seen_at = ? WHERE token IN (${placeholders})`).run(now.toISOString(), ...tokens).changes;\n }\n /**\n * Register or refresh a device. Three cases, resolved in one transaction:\n * 1. Known device (deviceId matches an existing row) → rotate token in place.\n * If the incoming token is currently held by a DIFFERENT row, APNs has\n * recycled it to us, so that stale row is hard-deleted first — otherwise\n * the rotation UPDATE would violate UNIQUE(token). (A soft-delete would\n * NOT free the constraint: the token value stays in the inactive row.)\n * 2. New legacy device (no deviceId) → deactivate prior null-device rows for\n * the platform (can't tell them apart) then insert/refresh by token.\n * 3. New device (deviceId not yet known) → insert; on token conflict, the\n * ownership WHERE guard gates the WHOLE update: an unknown device cannot\n * steal OR clobber the metadata of a token already owned by another active\n * device (the conflict resolves to a no-op, leaving that row untouched).\n */\n upsert(input, now = /* @__PURE__ */ new Date()) {\n const ts = now.toISOString();\n const appEnv = input.appEnv ?? (process.env.APNS_PRODUCTION === \"true\" ? \"production\" : \"sandbox\");\n const deviceId = input.deviceId ?? null;\n const friendlyName = input.friendlyName ?? null;\n const bundleId = input.bundleId ?? null;\n const { platform } = input;\n const token = input.token.trim();\n if (token.length === 0) {\n throw new Error(\"DeviceTokenStore.upsert: token must be a non-empty string\");\n }\n if (token.length > MAX_TOKEN_LENGTH) {\n throw new Error(`DeviceTokenStore.upsert: token exceeds ${MAX_TOKEN_LENGTH} chars`);\n }\n if (deviceId !== null && deviceId.length > MAX_DEVICE_ID_LENGTH) {\n throw new Error(`DeviceTokenStore.upsert: deviceId exceeds ${MAX_DEVICE_ID_LENGTH} chars`);\n }\n if (friendlyName !== null && friendlyName.length > MAX_NAME_LENGTH) {\n throw new Error(`DeviceTokenStore.upsert: friendlyName exceeds ${MAX_NAME_LENGTH} chars`);\n }\n if (bundleId !== null && bundleId.length > MAX_BUNDLE_ID_LENGTH) {\n throw new Error(`DeviceTokenStore.upsert: bundleId exceeds ${MAX_BUNDLE_ID_LENGTH} chars`);\n }\n this.db.transaction(() => {\n if (deviceId !== null) {\n const ownRow = this.db.prepare(\"SELECT id FROM device_tokens WHERE device_id = ? AND platform = ?\").get(deviceId, platform);\n if (ownRow) {\n this.db.prepare(\"DELETE FROM device_tokens WHERE token = ? AND id != ?\").run(token, ownRow.id);\n this.db.prepare(\n `UPDATE device_tokens SET\n token = ?,\n last_seen_at = ?,\n app_env = ?,\n failure_count = 0,\n is_active = 1,\n friendly_name = COALESCE(?, friendly_name),\n bundle_id = COALESCE(?, bundle_id)\n WHERE id = ?`\n ).run(token, ts, appEnv, friendlyName, bundleId, ownRow.id);\n return;\n }\n } else {\n this.db.prepare(\n `UPDATE device_tokens SET is_active = 0\n WHERE platform = ? AND device_id IS NULL AND token != ? AND is_active = 1`\n ).run(platform, token);\n }\n this.db.prepare(\n `INSERT INTO device_tokens\n (id, token, platform, app_env, device_id, friendly_name, bundle_id,\n registered_at, last_seen_at, failure_count, is_active)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1)\n ON CONFLICT(token) DO UPDATE SET\n last_seen_at = excluded.last_seen_at,\n app_env = excluded.app_env,\n failure_count = 0,\n is_active = 1,\n friendly_name = COALESCE(excluded.friendly_name, device_tokens.friendly_name),\n bundle_id = COALESCE(excluded.bundle_id, device_tokens.bundle_id),\n device_id = excluded.device_id\n WHERE device_tokens.device_id IS NULL\n OR device_tokens.device_id = excluded.device_id\n OR device_tokens.is_active = 0`\n ).run(randomUUID(), token, platform, appEnv, deviceId, friendlyName, bundleId, ts, ts);\n })();\n const rec = this.getByToken(token);\n if (!rec) {\n throw new Error(\"DeviceTokenStore.upsert: row not found after write\");\n }\n return rec;\n }\n importFile(filePath, renameSuffix, now) {\n if (!fs.existsSync(filePath)) {\n return;\n }\n try {\n const raw = JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n this.importTokenMap(raw, now);\n fs.renameSync(filePath, filePath + renameSuffix);\n } catch (err) {\n console.warn(`[device-token-store] failed to import ${filePath}:`, err);\n }\n }\n importTokenMap(raw, now) {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return;\n }\n const obj = raw;\n const ts = now.toISOString();\n const appEnv = process.env.APNS_PRODUCTION === \"true\" ? \"production\" : \"sandbox\";\n const insert = this.db.prepare(\n `INSERT OR IGNORE INTO device_tokens\n (id, token, platform, app_env, device_id, friendly_name, bundle_id,\n registered_at, last_seen_at, failure_count, is_active)\n VALUES (?, ?, ?, ?, NULL, NULL, NULL, ?, ?, 0, 1)`\n );\n for (const platform of [\"ios\", \"android\"]) {\n const value = obj[platform];\n const tokens = Array.isArray(value) ? value : typeof value === \"string\" ? [value] : [];\n for (const tok of tokens) {\n if (typeof tok !== \"string\") {\n continue;\n }\n const normalized = tok.trim();\n if (normalized.length > 0) {\n insert.run(randomUUID(), normalized, platform, appEnv, ts, ts);\n }\n }\n }\n }\n}\nfunction rowToRecord(row) {\n const rec = {\n appEnv: row.app_env,\n bundleId: row.bundle_id ?? null,\n deviceId: row.device_id ?? null,\n failureCount: row.failure_count,\n friendlyName: row.friendly_name ?? null,\n id: row.id,\n isActive: row.is_active === 1,\n lastSeenAt: row.last_seen_at,\n platform: row.platform,\n registeredAt: row.registered_at,\n token: row.token\n };\n if (!isDeviceRecord(rec)) {\n throw new Error(`DeviceTokenStore: row failed DeviceRecord validation (id=${String(row.id)})`);\n }\n return rec;\n}\nconst DTS_GLOBAL_KEY = \"__shooter_device_token_store\";\nconst deviceTokenStore = globalThis[DTS_GLOBAL_KEY] || new DeviceTokenStore();\nglobalThis[DTS_GLOBAL_KEY] = deviceTokenStore;\nexport {\n MAX_TOKEN_LENGTH as M,\n MAX_DEVICE_ID_LENGTH as a,\n MAX_NAME_LENGTH as b,\n MAX_BUNDLE_ID_LENGTH as c,\n deviceTokenStore as d\n};\n"],"names":[],"mappings":"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"}
|