@threadbase-sh/streamer 1.48.0 → 1.49.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/dist/cli.cjs +195 -175
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +189 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +186 -168
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -5295,7 +5295,7 @@ function createLogsRoutes() {
|
|
|
5295
5295
|
|
|
5296
5296
|
// src/api/routes/misc.routes.ts
|
|
5297
5297
|
var import_node_child_process = require("child_process");
|
|
5298
|
-
var
|
|
5298
|
+
var import_node_crypto3 = require("crypto");
|
|
5299
5299
|
var import_hono11 = require("hono");
|
|
5300
5300
|
var import_os6 = require("os");
|
|
5301
5301
|
|
|
@@ -5589,6 +5589,167 @@ var PushRepository = class {
|
|
|
5589
5589
|
}
|
|
5590
5590
|
};
|
|
5591
5591
|
|
|
5592
|
+
// src/services/push/apnsClient.ts
|
|
5593
|
+
var import_node_crypto2 = require("crypto");
|
|
5594
|
+
var import_node_http2 = require("http2");
|
|
5595
|
+
var log3 = getLogger("apns");
|
|
5596
|
+
var APNS_HOST_SANDBOX = "api.sandbox.push.apple.com";
|
|
5597
|
+
var APNS_MAX_PAYLOAD_BYTES = 4096;
|
|
5598
|
+
var JWT_TTL_SECONDS = 3e3;
|
|
5599
|
+
var DEAD_TOKEN_REASONS = /* @__PURE__ */ new Set([
|
|
5600
|
+
"BadDeviceToken",
|
|
5601
|
+
"DeviceTokenNotForTopic",
|
|
5602
|
+
"Unregistered",
|
|
5603
|
+
"ExpiredToken"
|
|
5604
|
+
]);
|
|
5605
|
+
function base64url(input) {
|
|
5606
|
+
return Buffer.from(input).toString("base64url");
|
|
5607
|
+
}
|
|
5608
|
+
function readApnsCredentialsFromEnv(env = process.env) {
|
|
5609
|
+
const key = env.APNS_KEY;
|
|
5610
|
+
if (!key || key.trim().length === 0) return null;
|
|
5611
|
+
const keyId = env.APNS_KEY_ID?.trim();
|
|
5612
|
+
const teamId = env.APNS_TEAM_ID?.trim();
|
|
5613
|
+
const bundleId = env.APNS_BUNDLE_ID?.trim();
|
|
5614
|
+
if (!keyId || !teamId || !bundleId) return null;
|
|
5615
|
+
const host = env.APNS_HOST ?? APNS_HOST_SANDBOX;
|
|
5616
|
+
return { key, keyId, teamId, bundleId, host };
|
|
5617
|
+
}
|
|
5618
|
+
function describeMissingApnsCredentials(env = process.env) {
|
|
5619
|
+
if (!env.APNS_KEY || env.APNS_KEY.trim().length === 0) {
|
|
5620
|
+
return "APNS_KEY is not set, so Live Activity push is disabled. Set it to the p8 key contents (not a path) to enable it.";
|
|
5621
|
+
}
|
|
5622
|
+
const missing = [
|
|
5623
|
+
["APNS_KEY_ID", env.APNS_KEY_ID],
|
|
5624
|
+
["APNS_TEAM_ID", env.APNS_TEAM_ID],
|
|
5625
|
+
["APNS_BUNDLE_ID", env.APNS_BUNDLE_ID]
|
|
5626
|
+
].filter(([, value]) => !value || value.trim().length === 0).map(([name]) => name);
|
|
5627
|
+
if (missing.length === 0) return null;
|
|
5628
|
+
return `APNS_KEY is set but ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not, so Live Activity push is disabled. Under launchd, APNS_KEY_ID is derived from the AuthKey_<keyId>.p8 filename; the team and bundle ids must be set explicitly.`;
|
|
5629
|
+
}
|
|
5630
|
+
var ApnsClient = class {
|
|
5631
|
+
constructor(creds) {
|
|
5632
|
+
this.creds = creds;
|
|
5633
|
+
}
|
|
5634
|
+
creds;
|
|
5635
|
+
session = null;
|
|
5636
|
+
cachedJwt = null;
|
|
5637
|
+
/**
|
|
5638
|
+
* The `apns-topic` for Live Activity pushes.
|
|
5639
|
+
*
|
|
5640
|
+
* The `.push-type.liveactivity` suffix is mandatory and is why the signing key
|
|
5641
|
+
* must be Team Scoped (All Topics) — a key scoped to the bundle id alone
|
|
5642
|
+
* cannot sign this topic.
|
|
5643
|
+
*/
|
|
5644
|
+
get topic() {
|
|
5645
|
+
return `${this.creds.bundleId}.push-type.liveactivity`;
|
|
5646
|
+
}
|
|
5647
|
+
/**
|
|
5648
|
+
* Mint or reuse the provider JWT.
|
|
5649
|
+
*
|
|
5650
|
+
* ES256 over the p8 key. Cached until shortly before expiry: Apple rejects a
|
|
5651
|
+
* token older than an hour, but minting one per request is wasteful and can
|
|
5652
|
+
* trip APNs' provider-token-update throttle.
|
|
5653
|
+
*/
|
|
5654
|
+
getJwt(now = Date.now()) {
|
|
5655
|
+
const nowSeconds = Math.floor(now / 1e3);
|
|
5656
|
+
if (this.cachedJwt && this.cachedJwt.expiresAt > nowSeconds + 60) {
|
|
5657
|
+
return this.cachedJwt.token;
|
|
5658
|
+
}
|
|
5659
|
+
const header = base64url(JSON.stringify({ alg: "ES256", kid: this.creds.keyId, typ: "JWT" }));
|
|
5660
|
+
const payload = base64url(JSON.stringify({ iss: this.creds.teamId, iat: nowSeconds }));
|
|
5661
|
+
const signingInput = `${header}.${payload}`;
|
|
5662
|
+
const signature = (0, import_node_crypto2.createSign)("SHA256").update(signingInput).sign({ key: this.creds.key, dsaEncoding: "ieee-p1363" });
|
|
5663
|
+
const token = `${signingInput}.${base64url(signature)}`;
|
|
5664
|
+
this.cachedJwt = { token, expiresAt: nowSeconds + JWT_TTL_SECONDS };
|
|
5665
|
+
return token;
|
|
5666
|
+
}
|
|
5667
|
+
/**
|
|
5668
|
+
* Reuse one HTTP/2 session across sends.
|
|
5669
|
+
*
|
|
5670
|
+
* APNs expects a long-lived connection; a fresh TLS handshake per push is slow
|
|
5671
|
+
* and Apple treats connection churn as abuse.
|
|
5672
|
+
*/
|
|
5673
|
+
getSession() {
|
|
5674
|
+
if (this.session && !this.session.closed && !this.session.destroyed) {
|
|
5675
|
+
return this.session;
|
|
5676
|
+
}
|
|
5677
|
+
const session = (0, import_node_http2.connect)(`https://${this.creds.host}`);
|
|
5678
|
+
session.on("error", (err) => {
|
|
5679
|
+
log3.warn("apns.session_error", { event: "apns.session_error", err: String(err) });
|
|
5680
|
+
});
|
|
5681
|
+
this.session = session;
|
|
5682
|
+
return session;
|
|
5683
|
+
}
|
|
5684
|
+
/**
|
|
5685
|
+
* Send one push.
|
|
5686
|
+
*
|
|
5687
|
+
* Resolves with a result rather than rejecting on an APNs rejection: a
|
|
5688
|
+
* rejected push is an expected outcome the caller must act on (retire the
|
|
5689
|
+
* token), not an exception. Only a genuinely unexpected local failure throws,
|
|
5690
|
+
* and the caller logs it.
|
|
5691
|
+
*/
|
|
5692
|
+
async send(args) {
|
|
5693
|
+
const body = Buffer.from(JSON.stringify(args.payload), "utf-8");
|
|
5694
|
+
if (body.byteLength > APNS_MAX_PAYLOAD_BYTES) {
|
|
5695
|
+
throw new Error(
|
|
5696
|
+
`APNs payload is ${body.byteLength} bytes, over the ${APNS_MAX_PAYLOAD_BYTES} byte limit`
|
|
5697
|
+
);
|
|
5698
|
+
}
|
|
5699
|
+
const session = this.getSession();
|
|
5700
|
+
const headers = {
|
|
5701
|
+
[import_node_http2.constants.HTTP2_HEADER_METHOD]: "POST",
|
|
5702
|
+
[import_node_http2.constants.HTTP2_HEADER_PATH]: `/3/device/${args.deviceToken}`,
|
|
5703
|
+
[import_node_http2.constants.HTTP2_HEADER_AUTHORIZATION]: `bearer ${this.getJwt()}`,
|
|
5704
|
+
"apns-push-type": "liveactivity",
|
|
5705
|
+
"apns-topic": this.topic,
|
|
5706
|
+
"apns-priority": String(args.priority ?? 10),
|
|
5707
|
+
...args.expirationSeconds != null && {
|
|
5708
|
+
"apns-expiration": String(args.expirationSeconds)
|
|
5709
|
+
},
|
|
5710
|
+
[import_node_http2.constants.HTTP2_HEADER_CONTENT_TYPE]: "application/json",
|
|
5711
|
+
[import_node_http2.constants.HTTP2_HEADER_CONTENT_LENGTH]: String(body.byteLength)
|
|
5712
|
+
};
|
|
5713
|
+
return new Promise((resolve2, reject) => {
|
|
5714
|
+
const req = session.request(headers);
|
|
5715
|
+
req.setTimeout(args.timeoutMs ?? 1e4, () => {
|
|
5716
|
+
req.close(import_node_http2.constants.NGHTTP2_CANCEL);
|
|
5717
|
+
resolve2({ ok: false, status: 0, reason: "Timeout", tokenDead: false });
|
|
5718
|
+
});
|
|
5719
|
+
let status = 0;
|
|
5720
|
+
req.on("response", (resHeaders) => {
|
|
5721
|
+
status = Number(resHeaders[import_node_http2.constants.HTTP2_HEADER_STATUS] ?? 0);
|
|
5722
|
+
});
|
|
5723
|
+
const chunks = [];
|
|
5724
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
5725
|
+
req.on("error", reject);
|
|
5726
|
+
req.on("end", () => {
|
|
5727
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
5728
|
+
let reason;
|
|
5729
|
+
if (raw.length > 0) {
|
|
5730
|
+
try {
|
|
5731
|
+
reason = JSON.parse(raw).reason;
|
|
5732
|
+
} catch {
|
|
5733
|
+
reason = raw.slice(0, 200);
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5736
|
+
resolve2({
|
|
5737
|
+
ok: status === 200,
|
|
5738
|
+
status,
|
|
5739
|
+
reason,
|
|
5740
|
+
tokenDead: reason != null && DEAD_TOKEN_REASONS.has(reason)
|
|
5741
|
+
});
|
|
5742
|
+
});
|
|
5743
|
+
req.end(body);
|
|
5744
|
+
});
|
|
5745
|
+
}
|
|
5746
|
+
/** Close the shared connection. Called on server shutdown. */
|
|
5747
|
+
close() {
|
|
5748
|
+
this.session?.close();
|
|
5749
|
+
this.session = null;
|
|
5750
|
+
}
|
|
5751
|
+
};
|
|
5752
|
+
|
|
5592
5753
|
// src/api/routes/misc.routes.ts
|
|
5593
5754
|
function numberOrNull(value) {
|
|
5594
5755
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
@@ -5619,11 +5780,22 @@ function readRawBody4(req) {
|
|
|
5619
5780
|
function verifyWebhookSignature(body, header, secret) {
|
|
5620
5781
|
if (!header) return false;
|
|
5621
5782
|
const provided = header.startsWith("sha256=") ? header.slice(7) : header;
|
|
5622
|
-
const expected = (0,
|
|
5783
|
+
const expected = (0, import_node_crypto3.createHmac)("sha256", secret).update(body).digest("hex");
|
|
5623
5784
|
const a = Buffer.from(provided, "utf-8");
|
|
5624
5785
|
const b = Buffer.from(expected, "utf-8");
|
|
5625
5786
|
if (a.length !== b.length) return false;
|
|
5626
|
-
return (0,
|
|
5787
|
+
return (0, import_node_crypto3.timingSafeEqual)(a, b);
|
|
5788
|
+
}
|
|
5789
|
+
function describePushCapability(liveActivityEnabled, env = process.env) {
|
|
5790
|
+
if (liveActivityEnabled) return { liveActivity: true, notifications: false };
|
|
5791
|
+
return {
|
|
5792
|
+
liveActivity: false,
|
|
5793
|
+
notifications: false,
|
|
5794
|
+
// describeMissingApnsCredentials only explains a *credential* gap and
|
|
5795
|
+
// returns null once the credentials are complete — reachable here, because
|
|
5796
|
+
// an unavailable token store disables the feature with the key still set.
|
|
5797
|
+
liveActivityReason: describeMissingApnsCredentials(env) ?? "APNs credentials are set but the push token store is unavailable, so Live Activity push is disabled."
|
|
5798
|
+
};
|
|
5627
5799
|
}
|
|
5628
5800
|
var clientLog = getLogger("client");
|
|
5629
5801
|
var createMiscRoutes = (deps) => {
|
|
@@ -5646,7 +5818,12 @@ var createMiscRoutes = (deps) => {
|
|
|
5646
5818
|
featureFlags: true,
|
|
5647
5819
|
// Same contract: this server serves GET /api/projects/summary, which the
|
|
5648
5820
|
// Hub's grouped views need before they can draw a tree.
|
|
5649
|
-
projectSummary: true
|
|
5821
|
+
projectSummary: true,
|
|
5822
|
+
// Delivery capability, not endpoint support: whether this server can
|
|
5823
|
+
// actually send a push, so mobile can hide an affordance instead of
|
|
5824
|
+
// registering tokens nothing will ever send to. Absent on older servers,
|
|
5825
|
+
// which a client should read as "unknown", not "unavailable".
|
|
5826
|
+
push: describePushCapability(deps.liveActivityPushEnabled())
|
|
5650
5827
|
});
|
|
5651
5828
|
});
|
|
5652
5829
|
app.get("/api/profiles", (c) => c.json([]));
|
|
@@ -5707,9 +5884,10 @@ var createMiscRoutes = (deps) => {
|
|
|
5707
5884
|
return c.json({ ok: true });
|
|
5708
5885
|
});
|
|
5709
5886
|
app.get("/api/push/health", (c) => {
|
|
5887
|
+
const push = describePushCapability(deps.liveActivityPushEnabled());
|
|
5710
5888
|
const repo = deps.pushRepo();
|
|
5711
|
-
if (!repo) return c.json({ tokens: [], available: false });
|
|
5712
|
-
return c.json({ tokens: repo.listHealth(), available: true });
|
|
5889
|
+
if (!repo) return c.json({ tokens: [], available: false, push });
|
|
5890
|
+
return c.json({ tokens: repo.listHealth(), available: true, push });
|
|
5713
5891
|
});
|
|
5714
5892
|
app.post("/api/__update", async (c) => {
|
|
5715
5893
|
const cfg = loadUpdateConfig();
|
|
@@ -6208,7 +6386,7 @@ var import_path12 = require("path");
|
|
|
6208
6386
|
var import_promises4 = require("timers/promises");
|
|
6209
6387
|
|
|
6210
6388
|
// src/db/query-timing.ts
|
|
6211
|
-
var
|
|
6389
|
+
var log4 = getLogger("db");
|
|
6212
6390
|
var DEFAULT_SLOW_QUERY_MS = 35;
|
|
6213
6391
|
var LABEL = /* @__PURE__ */ Symbol("tbQueryLabel");
|
|
6214
6392
|
function deriveLabel(sql) {
|
|
@@ -6224,15 +6402,15 @@ function resolveSlowMs() {
|
|
|
6224
6402
|
}
|
|
6225
6403
|
function record(label, ms, rows, slowMs) {
|
|
6226
6404
|
if (slowMs > 0 && ms >= slowMs) {
|
|
6227
|
-
|
|
6405
|
+
log4.warn(
|
|
6228
6406
|
`[db] slow query ${label} ${ms.toFixed(1)}ms rows=${rows}`,
|
|
6229
6407
|
{ event: "db.slow_query", stmt: label, ms: Math.round(ms * 100) / 100, rows },
|
|
6230
6408
|
"pino"
|
|
6231
6409
|
);
|
|
6232
6410
|
return;
|
|
6233
6411
|
}
|
|
6234
|
-
if (
|
|
6235
|
-
|
|
6412
|
+
if (log4.pino.isLevelEnabled("debug")) {
|
|
6413
|
+
log4.debug(
|
|
6236
6414
|
`[db] ${label} ${ms.toFixed(2)}ms rows=${rows}`,
|
|
6237
6415
|
{ event: "db.query", stmt: label, ms: Math.round(ms * 100) / 100, rows },
|
|
6238
6416
|
"pino"
|
|
@@ -9093,167 +9271,6 @@ function deriveProjectChatTitle(input) {
|
|
|
9093
9271
|
return `Untitled \xB7 ${input.id.slice(0, 8)}`;
|
|
9094
9272
|
}
|
|
9095
9273
|
|
|
9096
|
-
// src/services/push/apnsClient.ts
|
|
9097
|
-
var import_node_crypto3 = require("crypto");
|
|
9098
|
-
var import_node_http2 = require("http2");
|
|
9099
|
-
var log4 = getLogger("apns");
|
|
9100
|
-
var APNS_HOST_SANDBOX = "api.sandbox.push.apple.com";
|
|
9101
|
-
var APNS_MAX_PAYLOAD_BYTES = 4096;
|
|
9102
|
-
var JWT_TTL_SECONDS = 3e3;
|
|
9103
|
-
var DEAD_TOKEN_REASONS = /* @__PURE__ */ new Set([
|
|
9104
|
-
"BadDeviceToken",
|
|
9105
|
-
"DeviceTokenNotForTopic",
|
|
9106
|
-
"Unregistered",
|
|
9107
|
-
"ExpiredToken"
|
|
9108
|
-
]);
|
|
9109
|
-
function base64url(input) {
|
|
9110
|
-
return Buffer.from(input).toString("base64url");
|
|
9111
|
-
}
|
|
9112
|
-
function readApnsCredentialsFromEnv(env = process.env) {
|
|
9113
|
-
const key = env.APNS_KEY;
|
|
9114
|
-
if (!key || key.trim().length === 0) return null;
|
|
9115
|
-
const keyId = env.APNS_KEY_ID?.trim();
|
|
9116
|
-
const teamId = env.APNS_TEAM_ID?.trim();
|
|
9117
|
-
const bundleId = env.APNS_BUNDLE_ID?.trim();
|
|
9118
|
-
if (!keyId || !teamId || !bundleId) return null;
|
|
9119
|
-
const host = env.APNS_HOST ?? APNS_HOST_SANDBOX;
|
|
9120
|
-
return { key, keyId, teamId, bundleId, host };
|
|
9121
|
-
}
|
|
9122
|
-
function describeMissingApnsCredentials(env = process.env) {
|
|
9123
|
-
if (!env.APNS_KEY || env.APNS_KEY.trim().length === 0) {
|
|
9124
|
-
return "APNS_KEY is not set, so Live Activity push is disabled. Set it to the p8 key contents (not a path) to enable it.";
|
|
9125
|
-
}
|
|
9126
|
-
const missing = [
|
|
9127
|
-
["APNS_KEY_ID", env.APNS_KEY_ID],
|
|
9128
|
-
["APNS_TEAM_ID", env.APNS_TEAM_ID],
|
|
9129
|
-
["APNS_BUNDLE_ID", env.APNS_BUNDLE_ID]
|
|
9130
|
-
].filter(([, value]) => !value || value.trim().length === 0).map(([name]) => name);
|
|
9131
|
-
if (missing.length === 0) return null;
|
|
9132
|
-
return `APNS_KEY is set but ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not, so Live Activity push is disabled. Under launchd, APNS_KEY_ID is derived from the AuthKey_<keyId>.p8 filename; the team and bundle ids must be set explicitly.`;
|
|
9133
|
-
}
|
|
9134
|
-
var ApnsClient = class {
|
|
9135
|
-
constructor(creds) {
|
|
9136
|
-
this.creds = creds;
|
|
9137
|
-
}
|
|
9138
|
-
creds;
|
|
9139
|
-
session = null;
|
|
9140
|
-
cachedJwt = null;
|
|
9141
|
-
/**
|
|
9142
|
-
* The `apns-topic` for Live Activity pushes.
|
|
9143
|
-
*
|
|
9144
|
-
* The `.push-type.liveactivity` suffix is mandatory and is why the signing key
|
|
9145
|
-
* must be Team Scoped (All Topics) — a key scoped to the bundle id alone
|
|
9146
|
-
* cannot sign this topic.
|
|
9147
|
-
*/
|
|
9148
|
-
get topic() {
|
|
9149
|
-
return `${this.creds.bundleId}.push-type.liveactivity`;
|
|
9150
|
-
}
|
|
9151
|
-
/**
|
|
9152
|
-
* Mint or reuse the provider JWT.
|
|
9153
|
-
*
|
|
9154
|
-
* ES256 over the p8 key. Cached until shortly before expiry: Apple rejects a
|
|
9155
|
-
* token older than an hour, but minting one per request is wasteful and can
|
|
9156
|
-
* trip APNs' provider-token-update throttle.
|
|
9157
|
-
*/
|
|
9158
|
-
getJwt(now = Date.now()) {
|
|
9159
|
-
const nowSeconds = Math.floor(now / 1e3);
|
|
9160
|
-
if (this.cachedJwt && this.cachedJwt.expiresAt > nowSeconds + 60) {
|
|
9161
|
-
return this.cachedJwt.token;
|
|
9162
|
-
}
|
|
9163
|
-
const header = base64url(JSON.stringify({ alg: "ES256", kid: this.creds.keyId, typ: "JWT" }));
|
|
9164
|
-
const payload = base64url(JSON.stringify({ iss: this.creds.teamId, iat: nowSeconds }));
|
|
9165
|
-
const signingInput = `${header}.${payload}`;
|
|
9166
|
-
const signature = (0, import_node_crypto3.createSign)("SHA256").update(signingInput).sign({ key: this.creds.key, dsaEncoding: "ieee-p1363" });
|
|
9167
|
-
const token = `${signingInput}.${base64url(signature)}`;
|
|
9168
|
-
this.cachedJwt = { token, expiresAt: nowSeconds + JWT_TTL_SECONDS };
|
|
9169
|
-
return token;
|
|
9170
|
-
}
|
|
9171
|
-
/**
|
|
9172
|
-
* Reuse one HTTP/2 session across sends.
|
|
9173
|
-
*
|
|
9174
|
-
* APNs expects a long-lived connection; a fresh TLS handshake per push is slow
|
|
9175
|
-
* and Apple treats connection churn as abuse.
|
|
9176
|
-
*/
|
|
9177
|
-
getSession() {
|
|
9178
|
-
if (this.session && !this.session.closed && !this.session.destroyed) {
|
|
9179
|
-
return this.session;
|
|
9180
|
-
}
|
|
9181
|
-
const session = (0, import_node_http2.connect)(`https://${this.creds.host}`);
|
|
9182
|
-
session.on("error", (err) => {
|
|
9183
|
-
log4.warn("apns.session_error", { event: "apns.session_error", err: String(err) });
|
|
9184
|
-
});
|
|
9185
|
-
this.session = session;
|
|
9186
|
-
return session;
|
|
9187
|
-
}
|
|
9188
|
-
/**
|
|
9189
|
-
* Send one push.
|
|
9190
|
-
*
|
|
9191
|
-
* Resolves with a result rather than rejecting on an APNs rejection: a
|
|
9192
|
-
* rejected push is an expected outcome the caller must act on (retire the
|
|
9193
|
-
* token), not an exception. Only a genuinely unexpected local failure throws,
|
|
9194
|
-
* and the caller logs it.
|
|
9195
|
-
*/
|
|
9196
|
-
async send(args) {
|
|
9197
|
-
const body = Buffer.from(JSON.stringify(args.payload), "utf-8");
|
|
9198
|
-
if (body.byteLength > APNS_MAX_PAYLOAD_BYTES) {
|
|
9199
|
-
throw new Error(
|
|
9200
|
-
`APNs payload is ${body.byteLength} bytes, over the ${APNS_MAX_PAYLOAD_BYTES} byte limit`
|
|
9201
|
-
);
|
|
9202
|
-
}
|
|
9203
|
-
const session = this.getSession();
|
|
9204
|
-
const headers = {
|
|
9205
|
-
[import_node_http2.constants.HTTP2_HEADER_METHOD]: "POST",
|
|
9206
|
-
[import_node_http2.constants.HTTP2_HEADER_PATH]: `/3/device/${args.deviceToken}`,
|
|
9207
|
-
[import_node_http2.constants.HTTP2_HEADER_AUTHORIZATION]: `bearer ${this.getJwt()}`,
|
|
9208
|
-
"apns-push-type": "liveactivity",
|
|
9209
|
-
"apns-topic": this.topic,
|
|
9210
|
-
"apns-priority": String(args.priority ?? 10),
|
|
9211
|
-
...args.expirationSeconds != null && {
|
|
9212
|
-
"apns-expiration": String(args.expirationSeconds)
|
|
9213
|
-
},
|
|
9214
|
-
[import_node_http2.constants.HTTP2_HEADER_CONTENT_TYPE]: "application/json",
|
|
9215
|
-
[import_node_http2.constants.HTTP2_HEADER_CONTENT_LENGTH]: String(body.byteLength)
|
|
9216
|
-
};
|
|
9217
|
-
return new Promise((resolve2, reject) => {
|
|
9218
|
-
const req = session.request(headers);
|
|
9219
|
-
req.setTimeout(args.timeoutMs ?? 1e4, () => {
|
|
9220
|
-
req.close(import_node_http2.constants.NGHTTP2_CANCEL);
|
|
9221
|
-
resolve2({ ok: false, status: 0, reason: "Timeout", tokenDead: false });
|
|
9222
|
-
});
|
|
9223
|
-
let status = 0;
|
|
9224
|
-
req.on("response", (resHeaders) => {
|
|
9225
|
-
status = Number(resHeaders[import_node_http2.constants.HTTP2_HEADER_STATUS] ?? 0);
|
|
9226
|
-
});
|
|
9227
|
-
const chunks = [];
|
|
9228
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
9229
|
-
req.on("error", reject);
|
|
9230
|
-
req.on("end", () => {
|
|
9231
|
-
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
9232
|
-
let reason;
|
|
9233
|
-
if (raw.length > 0) {
|
|
9234
|
-
try {
|
|
9235
|
-
reason = JSON.parse(raw).reason;
|
|
9236
|
-
} catch {
|
|
9237
|
-
reason = raw.slice(0, 200);
|
|
9238
|
-
}
|
|
9239
|
-
}
|
|
9240
|
-
resolve2({
|
|
9241
|
-
ok: status === 200,
|
|
9242
|
-
status,
|
|
9243
|
-
reason,
|
|
9244
|
-
tokenDead: reason != null && DEAD_TOKEN_REASONS.has(reason)
|
|
9245
|
-
});
|
|
9246
|
-
});
|
|
9247
|
-
req.end(body);
|
|
9248
|
-
});
|
|
9249
|
-
}
|
|
9250
|
-
/** Close the shared connection. Called on server shutdown. */
|
|
9251
|
-
close() {
|
|
9252
|
-
this.session?.close();
|
|
9253
|
-
this.session = null;
|
|
9254
|
-
}
|
|
9255
|
-
};
|
|
9256
|
-
|
|
9257
9274
|
// src/services/push/expoPushSender.ts
|
|
9258
9275
|
var log5 = getLogger("expo-push");
|
|
9259
9276
|
var EXPO_PUSH_ENDPOINT = "https://exp.host/--/api/v2/push/send";
|
|
@@ -11526,6 +11543,7 @@ var StreamerServer = class {
|
|
|
11526
11543
|
cache: () => this.cache,
|
|
11527
11544
|
cacheMonitor: () => this.cacheMonitor,
|
|
11528
11545
|
pushRepo: () => this.pushRepo,
|
|
11546
|
+
liveActivityPushEnabled: () => this.liveActivityNotifier !== null,
|
|
11529
11547
|
devicesRepo: () => this.devicesRepo,
|
|
11530
11548
|
projectsRepo: () => this.projectsRepo,
|
|
11531
11549
|
conversationsRepo: () => this.conversationsRepo,
|