@emmanuel-nike/ark-notify-js 0.1.7 → 0.2.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/index.cjs +200 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +200 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.cts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/server/index.cjs +229 -1
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +3 -2
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.js +229 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server-stream-BK0sRKrW.d.cts +32 -0
- package/dist/server-stream-Cqe0hzxt.d.ts +32 -0
- package/dist/{types-C3Ycb__r.d.cts → types-Aj_RUX12.d.cts} +16 -1
- package/dist/{types-C3Ycb__r.d.ts → types-Aj_RUX12.d.ts} +16 -1
- package/dist/{utils-DX22wDRi.d.ts → utils-BhL2YmY0.d.ts} +1 -1
- package/dist/{utils-BFAGOqsj.d.cts → utils-CGTwTv3w.d.cts} +1 -1
- package/package.json +1 -1
package/dist/server/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var crypto = require('crypto');
|
|
4
4
|
|
|
5
5
|
// src/server-auth.ts
|
|
6
|
-
var CLIENT_ID_PATTERN = /^[a-zA-Z0-9_
|
|
6
|
+
var CLIENT_ID_PATTERN = /^[a-zA-Z0-9_\-.@\+]{1,128}$/;
|
|
7
7
|
var DEFAULT_TTL_SECONDS = 3600;
|
|
8
8
|
var MAX_TTL_SECONDS = 86400;
|
|
9
9
|
var DEFAULT_CAPABILITIES = {
|
|
@@ -118,6 +118,234 @@ async function handleServerAuth(options) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// src/config.ts
|
|
122
|
+
var DEFAULT_BASE_URL = "https://ark-notify-933303906015.europe-north1.run.app";
|
|
123
|
+
var configuredBaseUrl;
|
|
124
|
+
function resolveBaseUrl(baseUrl) {
|
|
125
|
+
return (baseUrl ?? configuredBaseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/utils.ts
|
|
129
|
+
var ArkNotifyError = class extends Error {
|
|
130
|
+
constructor(status, body) {
|
|
131
|
+
super(body.message);
|
|
132
|
+
this.name = "ArkNotifyError";
|
|
133
|
+
this.status = status;
|
|
134
|
+
this.code = body.error;
|
|
135
|
+
this.retryAfterSec = body.retryAfterSec;
|
|
136
|
+
this.reason = body.reason;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function appendQueryParams(url, params) {
|
|
140
|
+
const query = Object.entries(params).filter((entry) => {
|
|
141
|
+
const value = entry[1];
|
|
142
|
+
return value !== void 0 && value !== null && value !== false && value !== "";
|
|
143
|
+
}).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
144
|
+
if (!query) return url;
|
|
145
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
146
|
+
return `${url}${separator}${query}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/server-stream.ts
|
|
150
|
+
function buildAppCredentialsHeaders(credentials) {
|
|
151
|
+
return {
|
|
152
|
+
"X-App-Key": credentials.appKey,
|
|
153
|
+
"X-App-Secret": credentials.secret
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function dispatchSseEvent(data, handlers) {
|
|
157
|
+
if (!data) return;
|
|
158
|
+
let parsed;
|
|
159
|
+
try {
|
|
160
|
+
parsed = JSON.parse(data);
|
|
161
|
+
} catch {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (parsed.type === "connected") {
|
|
165
|
+
handlers.onConnected(parsed);
|
|
166
|
+
handlers.onMessage(parsed);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (parsed.type === "event") {
|
|
170
|
+
handlers.onEvent(parsed);
|
|
171
|
+
handlers.onMessage(parsed);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (parsed.type === "presence") {
|
|
175
|
+
handlers.onPresence(parsed);
|
|
176
|
+
handlers.onMessage(parsed);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
handlers.onMessage(parsed);
|
|
180
|
+
}
|
|
181
|
+
async function readSseStream(body, onSseData, signal) {
|
|
182
|
+
const reader = body.getReader();
|
|
183
|
+
const decoder = new TextDecoder();
|
|
184
|
+
let buffer = "";
|
|
185
|
+
let currentEvent = null;
|
|
186
|
+
let dataLines = [];
|
|
187
|
+
const flushEvent = () => {
|
|
188
|
+
if (dataLines.length === 0 && currentEvent === null) return;
|
|
189
|
+
onSseData(dataLines.join("\n"));
|
|
190
|
+
currentEvent = null;
|
|
191
|
+
dataLines = [];
|
|
192
|
+
};
|
|
193
|
+
try {
|
|
194
|
+
while (!signal.aborted) {
|
|
195
|
+
const { done, value } = await reader.read();
|
|
196
|
+
if (done) break;
|
|
197
|
+
buffer += decoder.decode(value, { stream: true });
|
|
198
|
+
const lines = buffer.split("\n");
|
|
199
|
+
buffer = lines.pop() ?? "";
|
|
200
|
+
for (const rawLine of lines) {
|
|
201
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
202
|
+
if (line === "") {
|
|
203
|
+
flushEvent();
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (line.startsWith(":")) continue;
|
|
207
|
+
if (line.startsWith("event:")) {
|
|
208
|
+
currentEvent = line.slice(6).trimStart();
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (line.startsWith("data:")) {
|
|
212
|
+
dataLines.push(line.slice(5).trimStart());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (buffer.length > 0) {
|
|
217
|
+
const line = buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer;
|
|
218
|
+
if (line.startsWith("data:")) {
|
|
219
|
+
dataLines.push(line.slice(5).trimStart());
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
flushEvent();
|
|
223
|
+
} finally {
|
|
224
|
+
reader.releaseLock();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
var ArkNotifyServerStream = class {
|
|
228
|
+
constructor(config) {
|
|
229
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
230
|
+
this.abortController = null;
|
|
231
|
+
this.connectionId = null;
|
|
232
|
+
this.connecting = false;
|
|
233
|
+
this.config = { ...config, baseUrl: resolveBaseUrl(config.baseUrl) };
|
|
234
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
235
|
+
}
|
|
236
|
+
getConnectionId() {
|
|
237
|
+
return this.connectionId;
|
|
238
|
+
}
|
|
239
|
+
on(event, handler) {
|
|
240
|
+
if (!this.listeners.has(event)) {
|
|
241
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
242
|
+
}
|
|
243
|
+
this.listeners.get(event).add(handler);
|
|
244
|
+
return () => this.off(event, handler);
|
|
245
|
+
}
|
|
246
|
+
off(event, handler) {
|
|
247
|
+
this.listeners.get(event)?.delete(handler);
|
|
248
|
+
}
|
|
249
|
+
emit(event, ...args) {
|
|
250
|
+
for (const handler of this.listeners.get(event) ?? []) {
|
|
251
|
+
handler(...args);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async connect() {
|
|
255
|
+
if (this.abortController || this.connecting) return;
|
|
256
|
+
this.connecting = true;
|
|
257
|
+
const abortController = new AbortController();
|
|
258
|
+
this.abortController = abortController;
|
|
259
|
+
const base = this.config.baseUrl.replace(/\/$/, "");
|
|
260
|
+
const streamUrl = appendQueryParams(
|
|
261
|
+
`${base}/api/v1/apps/${this.config.appKey}/server-stream`,
|
|
262
|
+
{
|
|
263
|
+
channels: this.config.channels.join(","),
|
|
264
|
+
history: this.config.history ? "true" : void 0
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
try {
|
|
268
|
+
const response = await this.fetchFn(streamUrl, {
|
|
269
|
+
method: "GET",
|
|
270
|
+
headers: {
|
|
271
|
+
Accept: "text/event-stream",
|
|
272
|
+
...buildAppCredentialsHeaders(this.config.credentials)
|
|
273
|
+
},
|
|
274
|
+
signal: abortController.signal
|
|
275
|
+
});
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
let body;
|
|
278
|
+
try {
|
|
279
|
+
body = await response.json();
|
|
280
|
+
} catch {
|
|
281
|
+
body = { error: "request_failed", message: response.statusText };
|
|
282
|
+
}
|
|
283
|
+
throw new ArkNotifyError(response.status, body);
|
|
284
|
+
}
|
|
285
|
+
if (!response.body) {
|
|
286
|
+
throw new Error("Server stream response has no body");
|
|
287
|
+
}
|
|
288
|
+
void this.consumeStream(response.body, abortController);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (abortController.signal.aborted) return;
|
|
291
|
+
this.abortController = null;
|
|
292
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
293
|
+
this.emit("close");
|
|
294
|
+
} finally {
|
|
295
|
+
this.connecting = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async consumeStream(body, abortController) {
|
|
299
|
+
try {
|
|
300
|
+
await readSseStream(
|
|
301
|
+
body,
|
|
302
|
+
(data) => {
|
|
303
|
+
dispatchSseEvent(data, {
|
|
304
|
+
onConnected: (message) => {
|
|
305
|
+
this.connectionId = message.connection_id;
|
|
306
|
+
this.emit("connected", message);
|
|
307
|
+
},
|
|
308
|
+
onEvent: (message) => this.emit("event", message),
|
|
309
|
+
onPresence: (message) => this.emit("presence", message),
|
|
310
|
+
onMessage: (message) => this.emit("message", message)
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
abortController.signal
|
|
314
|
+
);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
if (abortController.signal.aborted) return;
|
|
317
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
318
|
+
} finally {
|
|
319
|
+
if (this.abortController === abortController) {
|
|
320
|
+
this.abortController = null;
|
|
321
|
+
this.connectionId = null;
|
|
322
|
+
this.emit("close");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
disconnect() {
|
|
327
|
+
if (!this.abortController) return;
|
|
328
|
+
this.abortController.abort();
|
|
329
|
+
}
|
|
330
|
+
bind(channel, event, handler) {
|
|
331
|
+
const listener = (message) => {
|
|
332
|
+
if (message.channel === channel && message.event === event) {
|
|
333
|
+
handler(message.data, message);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
return this.on("event", listener);
|
|
337
|
+
}
|
|
338
|
+
bindAll(channel, handler) {
|
|
339
|
+
const listener = (message) => {
|
|
340
|
+
if (message.channel === channel) {
|
|
341
|
+
handler(message.data, message);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
return this.on("event", listener);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
exports.ArkNotifyServerStream = ArkNotifyServerStream;
|
|
121
349
|
exports.buildConnectionToken = buildConnectionToken;
|
|
122
350
|
exports.createAuthorizedServerAuthResponse = createAuthorizedServerAuthResponse;
|
|
123
351
|
exports.createDeniedServerAuthResponse = createDeniedServerAuthResponse;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server-auth.ts"],"names":["createHmac"],"mappings":";;;;;AAYA,IAAM,iBAAA,GAAoB,2BAAA;AAC1B,IAAM,mBAAA,GAAsB,IAAA;AAC5B,IAAM,eAAA,GAAkB,KAAA;AAExB,IAAM,oBAAA,GAA+C;AAAA,EACnD,SAAA,EAAW,IAAA;AAAA,EACX,OAAA,EAAS,IAAA;AAAA,EACT,QAAA,EAAU;AACZ,CAAA;AAEO,SAAS,gBAAgB,QAAA,EAAuC;AACrE,EAAA,OAAO,OAAO,QAAA,KAAa,QAAA,IAAY,iBAAA,CAAkB,KAAK,QAAQ,CAAA;AACxE;AAEA,SAAS,sBACP,YAAA,EACwB;AACxB,EAAA,IAAI,CAAC,YAAA,IAAgB,OAAO,YAAA,KAAiB,QAAA,EAAU;AACrD,IAAA,OAAO,EAAE,GAAG,oBAAA,EAAqB;AAAA,EACnC;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,aAAa,SAAA,KAAc,KAAA;AAAA,IACtC,OAAA,EAAS,aAAa,OAAA,KAAY,KAAA;AAAA,IAClC,QAAA,EAAU,aAAa,QAAA,KAAa;AAAA,GACtC;AACF;AAEA,SAAS,gBAAgB,GAAA,EAA6B;AACpD,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,GAAA,KAAQ,IAAA,EAAM;AACrC,IAAA,OAAO,mBAAA;AAAA,EACT;AAEA,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,GAAG,CAAA,IAAK,OAAO,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,MAAM,0CAA0C,CAAA;AAAA,EAC5D;AAEA,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,eAAe,CAAA;AACtC;AAEA,SAAS,cAAc,OAAA,EAA0C;AAC/D,EAAA,OAAO,MAAA,CAAO,KAAK,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA,CAAE,SAAS,WAAW,CAAA;AAClE;AAEA,SAAS,+BAAA,CACP,MAAA,EACA,MAAA,EACA,cAAA,EACQ;AACR,EAAA,OAAOA,iBAAA,CAAW,QAAA,EAAU,MAAM,CAAA,CAAE,MAAA,CAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,cAAc,CAAA,CAAE,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA;AACxF;AAGO,SAAS,oBAAA,CACd,MAAA,EACA,MAAA,EACA,OAAA,EAKQ;AACR,EAAA,IAAI,CAAC,eAAA,CAAgB,OAAA,CAAQ,QAAQ,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,EAClE;AAEA,EAAA,MAAM,iBAAiB,aAAA,CAAc;AAAA,IACnC,WAAW,OAAA,CAAQ,QAAA;AAAA,IACnB,KAAK,OAAA,CAAQ,GAAA;AAAA,IACb,YAAA,EAAc,qBAAA,CAAsB,OAAA,CAAQ,YAAY;AAAA,GACzD,CAAA;AAED,EAAA,MAAM,SAAA,GAAY,+BAAA,CAAgC,MAAA,EAAQ,MAAA,EAAQ,cAAc,CAAA;AAChF,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,cAAc,IAAI,SAAS,CAAA,CAAA;AACjD;AAMO,SAAS,uBAAuB,IAAA,EAAyC;AAC9E,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACrC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAS,IAAA;AACf,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,SAAA,IAAa,MAAA,CAAO,QAAA;AAE7C,EAAA,IAAI,CAAC,eAAA,CAAgB,SAAS,CAAA,EAAG;AAC/B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,SAAA,IAAa,MAAA,CAAO,QAAA;AAC7C,EAAA,MAAM,eAAe,MAAA,CAAO,YAAA;AAC5B,EAAA,MAAM,MAAM,MAAA,CAAO,GAAA;AAEnB,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,SAAA,EACE,cAAc,MAAA,GACV,IAAA,GACA,aAAa,OAAO,SAAA,KAAc,WACjC,SAAA,GACD,IAAA;AAAA,IACN,YAAA,EACE,YAAA,IAAgB,OAAO,YAAA,KAAiB,WACnC,YAAA,GACD,IAAA;AAAA,IACN,GAAA,EAAK,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM;AAAA,GACvC;AACF;AAQO,SAAS,mCACd,OAAA,EAC4B;AAC5B,EAAA,IAAI,CAAC,eAAA,CAAgB,OAAA,CAAQ,QAAQ,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,EAClE;AAEA,EAAA,MAAM,YAAA,GAAe,qBAAA,CAAsB,OAAA,CAAQ,YAAY,CAAA;AAC/D,EAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,OAAA,CAAQ,GAAG,CAAA;AAEvC,EAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,IAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,KAAK,GAAA,EAAI,GAAI,GAAI,CAAA,GAAI,GAAA;AAC5C,IAAA,OAAO;AAAA,MACL,OAAO,oBAAA,CAAqB,OAAA,CAAQ,YAAY,MAAA,EAAQ,OAAA,CAAQ,YAAY,MAAA,EAAQ;AAAA,QAClF,UAAU,OAAA,CAAQ,QAAA;AAAA,QAClB,GAAA;AAAA,QACA;AAAA,OACD;AAAA,KACH;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,IAAA;AAAA,IACT,WAAW,OAAA,CAAQ,QAAA;AAAA,IACnB,YAAA;AAAA,IACA;AAAA,GACF;AACF;AAGO,SAAS,+BAA+B,MAAA,EAA2C;AACxF,EAAA,OAAO,MAAA,GAAS,EAAE,OAAA,EAAS,KAAA,EAAO,QAAO,GAAI,EAAE,SAAS,KAAA,EAAM;AAChE;AAMA,eAAsB,iBACpB,OAAA,EAC6B;AAC7B,EAAA,MAAM,QAAA,GAA+B,MAAM,OAAA,CAAQ,YAAA,CAAa,QAAQ,OAAO,CAAA;AAE/E,EAAA,IAAI,aAAa,KAAA,EAAO;AACtB,IAAA,OAAO,8BAAA,EAA+B;AAAA,EACxC;AAEA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,QAAA,IAAY,OAAA,CAAQ,OAAA,CAAQ,SAAA;AACtD,EAAA,IAAI,CAAC,eAAA,CAAgB,QAAQ,CAAA,EAAG;AAC9B,IAAA,OAAO,+BAA+B,mBAAmB,CAAA;AAAA,EAC3D;AAEA,EAAA,IAAI;AACF,IAAA,OAAO,kCAAA,CAAmC;AAAA,MACxC,QAAA;AAAA,MACA,YAAA,EAAc,QAAA,CAAS,YAAA,IAAgB,OAAA,CAAQ,QAAQ,YAAA,IAAgB,KAAA,CAAA;AAAA,MACvE,GAAA,EAAK,QAAA,CAAS,GAAA,IAAO,OAAA,CAAQ,QAAQ,GAAA,IAAO,KAAA,CAAA;AAAA,MAC5C,aAAa,OAAA,CAAQ;AAAA,KACtB,CAAA;AAAA,EACH,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,OAAA,GAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,iBAAA;AACrD,IAAA,OAAO,+BAA+B,OAAO,CAAA;AAAA,EAC/C;AACF","file":"index.cjs","sourcesContent":["import { createHmac } from 'node:crypto'\nimport type {\n ConnectionCapabilities,\n CreateAuthorizedServerAuthResponseOptions,\n HandleServerAuthOptions,\n ServerAuthApprovedResponse,\n ServerAuthDeniedResponse,\n ServerAuthRequest,\n ServerAuthResponse,\n ServerAuthDecision,\n} from './types'\n\nconst CLIENT_ID_PATTERN = /^[a-zA-Z0-9_\\-.@]{1,128}$/\nconst DEFAULT_TTL_SECONDS = 3600\nconst MAX_TTL_SECONDS = 86400\n\nconst DEFAULT_CAPABILITIES: ConnectionCapabilities = {\n subscribe: true,\n publish: true,\n presence: true,\n}\n\nexport function isValidClientId(clientId: unknown): clientId is string {\n return typeof clientId === 'string' && CLIENT_ID_PATTERN.test(clientId)\n}\n\nfunction normalizeCapabilities(\n capabilities?: Partial<ConnectionCapabilities> | null\n): ConnectionCapabilities {\n if (!capabilities || typeof capabilities !== 'object') {\n return { ...DEFAULT_CAPABILITIES }\n }\n\n return {\n subscribe: capabilities.subscribe !== false,\n publish: capabilities.publish !== false,\n presence: capabilities.presence !== false,\n }\n}\n\nfunction resolveTokenTtl(ttl?: number | null): number {\n if (ttl === undefined || ttl === null) {\n return DEFAULT_TTL_SECONDS\n }\n\n if (!Number.isInteger(ttl) || ttl <= 0) {\n throw new Error('ttl must be a positive integer (seconds)')\n }\n\n return Math.min(ttl, MAX_TTL_SECONDS)\n}\n\nfunction encodePayload(payload: Record<string, unknown>): string {\n return Buffer.from(JSON.stringify(payload)).toString('base64url')\n}\n\nfunction computeConnectionTokenSignature(\n secret: string,\n appKey: string,\n payloadEncoded: string\n): string {\n return createHmac('sha256', secret).update(`${appKey}.${payloadEncoded}`).digest('hex')\n}\n\n/** Build a signed connection token in Ark Notify format. */\nexport function buildConnectionToken(\n appKey: string,\n secret: string,\n options: {\n clientId: string\n exp: number\n capabilities?: Partial<ConnectionCapabilities>\n }\n): string {\n if (!isValidClientId(options.clientId)) {\n throw new Error('clientId must be 1-128 alphanumeric characters')\n }\n\n const payloadEncoded = encodePayload({\n client_id: options.clientId,\n exp: options.exp,\n capabilities: normalizeCapabilities(options.capabilities),\n })\n\n const signature = computeConnectionTokenSignature(secret, appKey, payloadEncoded)\n return `${appKey}.${payloadEncoded}.${signature}`\n}\n\n/**\n * Parse the JSON body Ark Notify sends to your `serverAuthUrl`.\n * Returns null when the payload is invalid.\n */\nexport function parseServerAuthRequest(body: unknown): ServerAuthRequest | null {\n if (!body || typeof body !== 'object') {\n return null\n }\n\n const record = body as Record<string, unknown>\n const client_id = record.client_id ?? record.clientId\n\n if (!isValidClientId(client_id)) {\n return null\n }\n\n const user_data = record.user_data ?? record.userData\n const capabilities = record.capabilities\n const ttl = record.ttl\n\n return {\n client_id,\n user_data:\n user_data === undefined\n ? null\n : user_data && typeof user_data === 'object'\n ? (user_data as Record<string, unknown>)\n : null,\n capabilities:\n capabilities && typeof capabilities === 'object'\n ? (capabilities as Partial<ConnectionCapabilities>)\n : null,\n ttl: typeof ttl === 'number' ? ttl : null,\n }\n}\n\n/**\n * Build an approved response for your `serverAuthUrl` webhook.\n *\n * - Without `credentials`: returns `{ allowed: true, client_id, ... }` (option A).\n * - With `credentials`: returns `{ token }` with a pre-signed token (option B).\n */\nexport function createAuthorizedServerAuthResponse(\n options: CreateAuthorizedServerAuthResponseOptions\n): ServerAuthApprovedResponse {\n if (!isValidClientId(options.clientId)) {\n throw new Error('clientId must be 1-128 alphanumeric characters')\n }\n\n const capabilities = normalizeCapabilities(options.capabilities)\n const ttl = resolveTokenTtl(options.ttl)\n\n if (options.credentials) {\n const exp = Math.floor(Date.now() / 1000) + ttl\n return {\n token: buildConnectionToken(options.credentials.appKey, options.credentials.secret, {\n clientId: options.clientId,\n exp,\n capabilities,\n }),\n }\n }\n\n return {\n allowed: true,\n client_id: options.clientId,\n capabilities,\n ttl,\n }\n}\n\n/** Build a denied response for your `serverAuthUrl` webhook. */\nexport function createDeniedServerAuthResponse(reason?: string): ServerAuthDeniedResponse {\n return reason ? { allowed: false, reason } : { allowed: false }\n}\n\n/**\n * Authenticate an incoming `serverAuthUrl` request and return the webhook response\n * body Ark Notify expects.\n */\nexport async function handleServerAuth(\n options: HandleServerAuthOptions\n): Promise<ServerAuthResponse> {\n const decision: ServerAuthDecision = await options.isAuthorized(options.request)\n\n if (decision === false) {\n return createDeniedServerAuthResponse()\n }\n\n const clientId = decision.clientId ?? options.request.client_id\n if (!isValidClientId(clientId)) {\n return createDeniedServerAuthResponse('invalid_client_id')\n }\n\n try {\n return createAuthorizedServerAuthResponse({\n clientId,\n capabilities: decision.capabilities ?? options.request.capabilities ?? undefined,\n ttl: decision.ttl ?? options.request.ttl ?? undefined,\n credentials: options.credentials,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : 'invalid_request'\n return createDeniedServerAuthResponse(message)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server-auth.ts","../../src/config.ts","../../src/utils.ts","../../src/server-stream.ts"],"names":["createHmac"],"mappings":";;;;;AAYA,IAAM,iBAAA,GAAoB,6BAAA;AAC1B,IAAM,mBAAA,GAAsB,IAAA;AAC5B,IAAM,eAAA,GAAkB,KAAA;AAExB,IAAM,oBAAA,GAA+C;AAAA,EACnD,SAAA,EAAW,IAAA;AAAA,EACX,OAAA,EAAS,IAAA;AAAA,EACT,QAAA,EAAU;AACZ,CAAA;AAEO,SAAS,gBAAgB,QAAA,EAAuC;AACrE,EAAA,OAAO,OAAO,QAAA,KAAa,QAAA,IAAY,iBAAA,CAAkB,KAAK,QAAQ,CAAA;AACxE;AAEA,SAAS,sBACP,YAAA,EACwB;AACxB,EAAA,IAAI,CAAC,YAAA,IAAgB,OAAO,YAAA,KAAiB,QAAA,EAAU;AACrD,IAAA,OAAO,EAAE,GAAG,oBAAA,EAAqB;AAAA,EACnC;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,aAAa,SAAA,KAAc,KAAA;AAAA,IACtC,OAAA,EAAS,aAAa,OAAA,KAAY,KAAA;AAAA,IAClC,QAAA,EAAU,aAAa,QAAA,KAAa;AAAA,GACtC;AACF;AAEA,SAAS,gBAAgB,GAAA,EAA6B;AACpD,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,GAAA,KAAQ,IAAA,EAAM;AACrC,IAAA,OAAO,mBAAA;AAAA,EACT;AAEA,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,GAAG,CAAA,IAAK,OAAO,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,MAAM,0CAA0C,CAAA;AAAA,EAC5D;AAEA,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,eAAe,CAAA;AACtC;AAEA,SAAS,cAAc,OAAA,EAA0C;AAC/D,EAAA,OAAO,MAAA,CAAO,KAAK,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA,CAAE,SAAS,WAAW,CAAA;AAClE;AAEA,SAAS,+BAAA,CACP,MAAA,EACA,MAAA,EACA,cAAA,EACQ;AACR,EAAA,OAAOA,iBAAA,CAAW,QAAA,EAAU,MAAM,CAAA,CAAE,MAAA,CAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,cAAc,CAAA,CAAE,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA;AACxF;AAGO,SAAS,oBAAA,CACd,MAAA,EACA,MAAA,EACA,OAAA,EAKQ;AACR,EAAA,IAAI,CAAC,eAAA,CAAgB,OAAA,CAAQ,QAAQ,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,EAClE;AAEA,EAAA,MAAM,iBAAiB,aAAA,CAAc;AAAA,IACnC,WAAW,OAAA,CAAQ,QAAA;AAAA,IACnB,KAAK,OAAA,CAAQ,GAAA;AAAA,IACb,YAAA,EAAc,qBAAA,CAAsB,OAAA,CAAQ,YAAY;AAAA,GACzD,CAAA;AAED,EAAA,MAAM,SAAA,GAAY,+BAAA,CAAgC,MAAA,EAAQ,MAAA,EAAQ,cAAc,CAAA;AAChF,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,cAAc,IAAI,SAAS,CAAA,CAAA;AACjD;AAMO,SAAS,uBAAuB,IAAA,EAAyC;AAC9E,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACrC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAS,IAAA;AACf,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,SAAA,IAAa,MAAA,CAAO,QAAA;AAE7C,EAAA,IAAI,CAAC,eAAA,CAAgB,SAAS,CAAA,EAAG;AAC/B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,SAAA,IAAa,MAAA,CAAO,QAAA;AAC7C,EAAA,MAAM,eAAe,MAAA,CAAO,YAAA;AAC5B,EAAA,MAAM,MAAM,MAAA,CAAO,GAAA;AAEnB,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,SAAA,EACE,cAAc,MAAA,GACV,IAAA,GACA,aAAa,OAAO,SAAA,KAAc,WACjC,SAAA,GACD,IAAA;AAAA,IACN,YAAA,EACE,YAAA,IAAgB,OAAO,YAAA,KAAiB,WACnC,YAAA,GACD,IAAA;AAAA,IACN,GAAA,EAAK,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM;AAAA,GACvC;AACF;AAQO,SAAS,mCACd,OAAA,EAC4B;AAC5B,EAAA,IAAI,CAAC,eAAA,CAAgB,OAAA,CAAQ,QAAQ,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,EAClE;AAEA,EAAA,MAAM,YAAA,GAAe,qBAAA,CAAsB,OAAA,CAAQ,YAAY,CAAA;AAC/D,EAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,OAAA,CAAQ,GAAG,CAAA;AAEvC,EAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,IAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,KAAK,GAAA,EAAI,GAAI,GAAI,CAAA,GAAI,GAAA;AAC5C,IAAA,OAAO;AAAA,MACL,OAAO,oBAAA,CAAqB,OAAA,CAAQ,YAAY,MAAA,EAAQ,OAAA,CAAQ,YAAY,MAAA,EAAQ;AAAA,QAClF,UAAU,OAAA,CAAQ,QAAA;AAAA,QAClB,GAAA;AAAA,QACA;AAAA,OACD;AAAA,KACH;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,IAAA;AAAA,IACT,WAAW,OAAA,CAAQ,QAAA;AAAA,IACnB,YAAA;AAAA,IACA;AAAA,GACF;AACF;AAGO,SAAS,+BAA+B,MAAA,EAA2C;AACxF,EAAA,OAAO,MAAA,GAAS,EAAE,OAAA,EAAS,KAAA,EAAO,QAAO,GAAI,EAAE,SAAS,KAAA,EAAM;AAChE;AAMA,eAAsB,iBACpB,OAAA,EAC6B;AAC7B,EAAA,MAAM,QAAA,GAA+B,MAAM,OAAA,CAAQ,YAAA,CAAa,QAAQ,OAAO,CAAA;AAE/E,EAAA,IAAI,aAAa,KAAA,EAAO;AACtB,IAAA,OAAO,8BAAA,EAA+B;AAAA,EACxC;AAEA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,QAAA,IAAY,OAAA,CAAQ,OAAA,CAAQ,SAAA;AACtD,EAAA,IAAI,CAAC,eAAA,CAAgB,QAAQ,CAAA,EAAG;AAC9B,IAAA,OAAO,+BAA+B,mBAAmB,CAAA;AAAA,EAC3D;AAEA,EAAA,IAAI;AACF,IAAA,OAAO,kCAAA,CAAmC;AAAA,MACxC,QAAA;AAAA,MACA,YAAA,EAAc,QAAA,CAAS,YAAA,IAAgB,OAAA,CAAQ,QAAQ,YAAA,IAAgB,KAAA,CAAA;AAAA,MACvE,GAAA,EAAK,QAAA,CAAS,GAAA,IAAO,OAAA,CAAQ,QAAQ,GAAA,IAAO,KAAA,CAAA;AAAA,MAC5C,aAAa,OAAA,CAAQ;AAAA,KACtB,CAAA;AAAA,EACH,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,OAAA,GAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,iBAAA;AACrD,IAAA,OAAO,+BAA+B,OAAO,CAAA;AAAA,EAC/C;AACF;;;ACjMO,IAAM,gBAAA,GAAmB,uDAAA;AAEhC,IAAI,iBAAA;AAaG,SAAS,eAAe,OAAA,EAA0B;AACvD,EAAA,OAAA,CAAQ,OAAA,IAAW,iBAAA,IAAqB,gBAAA,EAAkB,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC7E;;;ACfO,IAAM,cAAA,GAAN,cAA6B,KAAA,CAAM;AAAA,EAMxC,WAAA,CAAY,QAAgB,IAAA,EAAgB;AAC1C,IAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAClB,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,KAAA;AACjB,IAAA,IAAA,CAAK,gBAAgB,IAAA,CAAK,aAAA;AAC1B,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,MAAA;AAAA,EACrB;AACF,CAAA;AAWO,SAAS,iBAAA,CACd,KACA,MAAA,EACQ;AACR,EAAA,MAAM,QAAQ,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAChC,MAAA,CAAO,CAAC,KAAA,KAAqC;AAC5C,IAAA,MAAM,KAAA,GAAQ,MAAM,CAAC,CAAA;AACrB,IAAA,OAAO,UAAU,MAAA,IAAa,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,SAAS,KAAA,KAAU,EAAA;AAAA,EAC/E,CAAC,CAAA,CACA,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM,CAAA,EAAG,mBAAmB,GAAG,CAAC,IAAI,kBAAA,CAAmB,KAAK,CAAC,CAAA,CAAE,CAAA,CAC/E,KAAK,GAAG,CAAA;AAEX,EAAA,IAAI,CAAC,OAAO,OAAO,GAAA;AAEnB,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,QAAA,CAAS,GAAG,IAAI,GAAA,GAAM,GAAA;AAC5C,EAAA,OAAO,CAAA,EAAG,GAAG,CAAA,EAAG,SAAS,GAAG,KAAK,CAAA,CAAA;AACnC;;;ACrBA,SAAS,2BAA2B,WAAA,EAAqD;AACvF,EAAA,OAAO;AAAA,IACL,aAAa,WAAA,CAAY,MAAA;AAAA,IACzB,gBAAgB,WAAA,CAAY;AAAA,GAC9B;AACF;AAIA,SAAS,gBAAA,CACP,MACA,QAAA,EAMM;AACN,EAAA,IAAI,CAAC,IAAA,EAAM;AAEX,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,SAAS,WAAA,EAAa;AAC/B,IAAA,QAAA,CAAS,YAAY,MAAM,CAAA;AAC3B,IAAA,QAAA,CAAS,UAAU,MAAM,CAAA;AACzB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,SAAS,OAAA,EAAS;AAC3B,IAAA,QAAA,CAAS,QAAQ,MAAM,CAAA;AACvB,IAAA,QAAA,CAAS,UAAU,MAAM,CAAA;AACzB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,SAAS,UAAA,EAAY;AAC9B,IAAA,QAAA,CAAS,WAAW,MAAM,CAAA;AAC1B,IAAA,QAAA,CAAS,UAAU,MAAM,CAAA;AACzB,IAAA;AAAA,EACF;AAEA,EAAA,QAAA,CAAS,UAAU,MAAM,CAAA;AAC3B;AAEA,eAAe,aAAA,CACb,IAAA,EACA,SAAA,EACA,MAAA,EACe;AACf,EAAA,MAAM,MAAA,GAAS,KAAK,SAAA,EAAU;AAC9B,EAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,IAAI,YAAA,GAA8B,IAAA;AAClC,EAAA,IAAI,YAAsB,EAAC;AAE3B,EAAA,MAAM,aAAa,MAAM;AACvB,IAAA,IAAI,SAAA,CAAU,MAAA,KAAW,CAAA,IAAK,YAAA,KAAiB,IAAA,EAAM;AACrD,IAAA,SAAA,CAAU,SAAA,CAAU,IAAA,CAAK,IAAI,CAAC,CAAA;AAC9B,IAAA,YAAA,GAAe,IAAA;AACf,IAAA,SAAA,GAAY,EAAC;AAAA,EACf,CAAA;AAEA,EAAA,IAAI;AACF,IAAA,OAAO,CAAC,OAAO,OAAA,EAAS;AACtB,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,MAAA,IAAI,IAAA,EAAM;AAEV,MAAA,MAAA,IAAU,QAAQ,MAAA,CAAO,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAM,CAAA;AAChD,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAC/B,MAAA,MAAA,GAAS,KAAA,CAAM,KAAI,IAAK,EAAA;AAExB,MAAA,KAAA,MAAW,WAAW,KAAA,EAAO;AAC3B,QAAA,MAAM,IAAA,GAAO,QAAQ,QAAA,CAAS,IAAI,IAAI,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,CAAA,CAAE,CAAA,GAAI,OAAA;AAE7D,QAAA,IAAI,SAAS,EAAA,EAAI;AACf,UAAA,UAAA,EAAW;AACX,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAE1B,QAAA,IAAI,IAAA,CAAK,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC7B,UAAA,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,CAAE,SAAA,EAAU;AACvC,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA,EAAG;AAC5B,UAAA,SAAA,CAAU,KAAK,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,CAAE,WAAW,CAAA;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,MAAA,MAAM,IAAA,GAAO,OAAO,QAAA,CAAS,IAAI,IAAI,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,CAAE,CAAA,GAAI,MAAA;AAC3D,MAAA,IAAI,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA,EAAG;AAC5B,QAAA,SAAA,CAAU,KAAK,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,CAAE,WAAW,CAAA;AAAA,MAC1C;AAAA,IACF;AAEA,IAAA,UAAA,EAAW;AAAA,EACb,CAAA,SAAE;AACA,IAAA,MAAA,CAAO,WAAA,EAAY;AAAA,EACrB;AACF;AAEO,IAAM,wBAAN,MAA4B;AAAA,EAWjC,YAAY,MAAA,EAAqC;AARjD,IAAA,IAAA,CAAiB,SAAA,uBAAgB,GAAA,EAG/B;AACF,IAAA,IAAA,CAAQ,eAAA,GAA0C,IAAA;AAClD,IAAA,IAAA,CAAQ,YAAA,GAA8B,IAAA;AACtC,IAAA,IAAA,CAAQ,UAAA,GAAa,KAAA;AAGnB,IAAA,IAAA,CAAK,MAAA,GAAS,EAAE,GAAG,MAAA,EAAQ,SAAS,cAAA,CAAe,MAAA,CAAO,OAAO,CAAA,EAAE;AACnE,IAAA,IAAA,CAAK,UAAU,MAAA,CAAO,KAAA,IAAS,UAAA,CAAW,KAAA,CAAM,KAAK,UAAU,CAAA;AAAA,EACjE;AAAA,EAEA,eAAA,GAAiC;AAC/B,IAAA,OAAO,IAAA,CAAK,YAAA;AAAA,EACd;AAAA,EAEA,EAAA,CAAoC,OAAU,OAAA,EAA8C;AAC1F,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9B,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,kBAAO,IAAI,KAAK,CAAA;AAAA,IACrC;AACA,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,CAAG,IAAI,OAAO,CAAA;AACtC,IAAA,OAAO,MAAM,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,OAAO,CAAA;AAAA,EACtC;AAAA,EAEA,GAAA,CAAqC,OAAU,OAAA,EAAwC;AACrF,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,OAAO,OAAO,CAAA;AAAA,EAC3C;AAAA,EAEQ,IAAA,CACN,UACG,IAAA,EACG;AACN,IAAA,KAAA,MAAW,WAAW,IAAA,CAAK,SAAA,CAAU,IAAI,KAAK,CAAA,IAAK,EAAC,EAAG;AACpD,MAAC,OAAA,CAAgE,GAAG,IAAI,CAAA;AAAA,IAC3E;AAAA,EACF;AAAA,EAEA,MAAM,OAAA,GAAyB;AAC7B,IAAA,IAAI,IAAA,CAAK,eAAA,IAAmB,IAAA,CAAK,UAAA,EAAY;AAE7C,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAClB,IAAA,MAAM,eAAA,GAAkB,IAAI,eAAA,EAAgB;AAC5C,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAEvB,IAAA,MAAM,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,OAAA,CAAQ,OAAO,EAAE,CAAA;AAClD,IAAA,MAAM,SAAA,GAAY,iBAAA;AAAA,MAChB,CAAA,EAAG,IAAI,CAAA,aAAA,EAAgB,IAAA,CAAK,OAAO,MAAM,CAAA,cAAA,CAAA;AAAA,MACzC;AAAA,QACE,QAAA,EAAU,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,KAAK,GAAG,CAAA;AAAA,QACvC,OAAA,EAAS,IAAA,CAAK,MAAA,CAAO,OAAA,GAAU,MAAA,GAAS;AAAA;AAC1C,KACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW;AAAA,QAC7C,MAAA,EAAQ,KAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,MAAA,EAAQ,mBAAA;AAAA,UACR,GAAG,0BAAA,CAA2B,IAAA,CAAK,MAAA,CAAO,WAAW;AAAA,SACvD;AAAA,QACA,QAAQ,eAAA,CAAgB;AAAA,OACzB,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,IAAI,IAAA;AACJ,QAAA,IAAI;AACF,UAAA,IAAA,GAAO,MAAM,SAAS,IAAA,EAAK;AAAA,QAC7B,CAAA,CAAA,MAAQ;AACN,UAAA,IAAA,GAAO,EAAE,KAAA,EAAO,gBAAA,EAAkB,OAAA,EAAS,SAAS,UAAA,EAAW;AAAA,QACjE;AACA,QAAA,MAAM,IAAI,cAAA,CAAe,QAAA,CAAS,MAAA,EAAQ,IAAI,CAAA;AAAA,MAChD;AAEA,MAAA,IAAI,CAAC,SAAS,IAAA,EAAM;AAClB,QAAA,MAAM,IAAI,MAAM,oCAAoC,CAAA;AAAA,MACtD;AAEA,MAAA,KAAK,IAAA,CAAK,aAAA,CAAc,QAAA,CAAS,IAAA,EAAM,eAAe,CAAA;AAAA,IACxD,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,eAAA,CAAgB,OAAO,OAAA,EAAS;AACpC,MAAA,IAAA,CAAK,eAAA,GAAkB,IAAA;AACvB,MAAA,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AACtE,MAAA,IAAA,CAAK,KAAK,OAAO,CAAA;AAAA,IACnB,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAc,aAAA,CAAc,IAAA,EAAkC,eAAA,EAAiD;AAC7G,IAAA,IAAI;AACF,MAAA,MAAM,aAAA;AAAA,QACJ,IAAA;AAAA,QACA,CAAC,IAAA,KAAS;AACR,UAAA,gBAAA,CAAiB,IAAA,EAAM;AAAA,YACrB,WAAA,EAAa,CAAC,OAAA,KAAY;AACxB,cAAA,IAAA,CAAK,eAAe,OAAA,CAAQ,aAAA;AAC5B,cAAA,IAAA,CAAK,IAAA,CAAK,aAAa,OAAO,CAAA;AAAA,YAChC,CAAA;AAAA,YACA,SAAS,CAAC,OAAA,KAAY,IAAA,CAAK,IAAA,CAAK,SAAS,OAAO,CAAA;AAAA,YAChD,YAAY,CAAC,OAAA,KAAY,IAAA,CAAK,IAAA,CAAK,YAAY,OAAO,CAAA;AAAA,YACtD,WAAW,CAAC,OAAA,KAAY,IAAA,CAAK,IAAA,CAAK,WAAW,OAAO;AAAA,WACrD,CAAA;AAAA,QACH,CAAA;AAAA,QACA,eAAA,CAAgB;AAAA,OAClB;AAAA,IACF,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,eAAA,CAAgB,OAAO,OAAA,EAAS;AACpC,MAAA,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAAA,IACxE,CAAA,SAAE;AACA,MAAA,IAAI,IAAA,CAAK,oBAAoB,eAAA,EAAiB;AAC5C,QAAA,IAAA,CAAK,eAAA,GAAkB,IAAA;AACvB,QAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,QAAA,IAAA,CAAK,KAAK,OAAO,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAA,GAAmB;AACjB,IAAA,IAAI,CAAC,KAAK,eAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,gBAAgB,KAAA,EAAM;AAAA,EAC7B;AAAA,EAEA,IAAA,CACE,OAAA,EACA,KAAA,EACA,OAAA,EACY;AACZ,IAAA,MAAM,QAAA,GAAW,CAAC,OAAA,KAA0B;AAC1C,MAAA,IAAI,OAAA,CAAQ,OAAA,KAAY,OAAA,IAAW,OAAA,CAAQ,UAAU,KAAA,EAAO;AAC1D,QAAA,OAAA,CAAQ,OAAA,CAAQ,MAAM,OAAO,CAAA;AAAA,MAC/B;AAAA,IACF,CAAA;AACA,IAAA,OAAO,IAAA,CAAK,EAAA,CAAG,OAAA,EAAS,QAAQ,CAAA;AAAA,EAClC;AAAA,EAEA,OAAA,CAAQ,SAAiB,OAAA,EAAqE;AAC5F,IAAA,MAAM,QAAA,GAAW,CAAC,OAAA,KAA0B;AAC1C,MAAA,IAAI,OAAA,CAAQ,YAAY,OAAA,EAAS;AAC/B,QAAA,OAAA,CAAQ,OAAA,CAAQ,MAAM,OAAO,CAAA;AAAA,MAC/B;AAAA,IACF,CAAA;AACA,IAAA,OAAO,IAAA,CAAK,EAAA,CAAG,OAAA,EAAS,QAAQ,CAAA;AAAA,EAClC;AACF","file":"index.cjs","sourcesContent":["import { createHmac } from 'node:crypto'\nimport type {\n ConnectionCapabilities,\n CreateAuthorizedServerAuthResponseOptions,\n HandleServerAuthOptions,\n ServerAuthApprovedResponse,\n ServerAuthDeniedResponse,\n ServerAuthRequest,\n ServerAuthResponse,\n ServerAuthDecision,\n} from './types'\n\nconst CLIENT_ID_PATTERN = /^[a-zA-Z0-9_\\-.@\\+]{1,128}$/\nconst DEFAULT_TTL_SECONDS = 3600\nconst MAX_TTL_SECONDS = 86400\n\nconst DEFAULT_CAPABILITIES: ConnectionCapabilities = {\n subscribe: true,\n publish: true,\n presence: true,\n}\n\nexport function isValidClientId(clientId: unknown): clientId is string {\n return typeof clientId === 'string' && CLIENT_ID_PATTERN.test(clientId)\n}\n\nfunction normalizeCapabilities(\n capabilities?: Partial<ConnectionCapabilities> | null\n): ConnectionCapabilities {\n if (!capabilities || typeof capabilities !== 'object') {\n return { ...DEFAULT_CAPABILITIES }\n }\n\n return {\n subscribe: capabilities.subscribe !== false,\n publish: capabilities.publish !== false,\n presence: capabilities.presence !== false,\n }\n}\n\nfunction resolveTokenTtl(ttl?: number | null): number {\n if (ttl === undefined || ttl === null) {\n return DEFAULT_TTL_SECONDS\n }\n\n if (!Number.isInteger(ttl) || ttl <= 0) {\n throw new Error('ttl must be a positive integer (seconds)')\n }\n\n return Math.min(ttl, MAX_TTL_SECONDS)\n}\n\nfunction encodePayload(payload: Record<string, unknown>): string {\n return Buffer.from(JSON.stringify(payload)).toString('base64url')\n}\n\nfunction computeConnectionTokenSignature(\n secret: string,\n appKey: string,\n payloadEncoded: string\n): string {\n return createHmac('sha256', secret).update(`${appKey}.${payloadEncoded}`).digest('hex')\n}\n\n/** Build a signed connection token in Ark Notify format. */\nexport function buildConnectionToken(\n appKey: string,\n secret: string,\n options: {\n clientId: string\n exp: number\n capabilities?: Partial<ConnectionCapabilities>\n }\n): string {\n if (!isValidClientId(options.clientId)) {\n throw new Error('clientId must be 1-128 alphanumeric characters')\n }\n\n const payloadEncoded = encodePayload({\n client_id: options.clientId,\n exp: options.exp,\n capabilities: normalizeCapabilities(options.capabilities),\n })\n\n const signature = computeConnectionTokenSignature(secret, appKey, payloadEncoded)\n return `${appKey}.${payloadEncoded}.${signature}`\n}\n\n/**\n * Parse the JSON body Ark Notify sends to your `serverAuthUrl`.\n * Returns null when the payload is invalid.\n */\nexport function parseServerAuthRequest(body: unknown): ServerAuthRequest | null {\n if (!body || typeof body !== 'object') {\n return null\n }\n\n const record = body as Record<string, unknown>\n const client_id = record.client_id ?? record.clientId\n\n if (!isValidClientId(client_id)) {\n return null\n }\n\n const user_data = record.user_data ?? record.userData\n const capabilities = record.capabilities\n const ttl = record.ttl\n\n return {\n client_id,\n user_data:\n user_data === undefined\n ? null\n : user_data && typeof user_data === 'object'\n ? (user_data as Record<string, unknown>)\n : null,\n capabilities:\n capabilities && typeof capabilities === 'object'\n ? (capabilities as Partial<ConnectionCapabilities>)\n : null,\n ttl: typeof ttl === 'number' ? ttl : null,\n }\n}\n\n/**\n * Build an approved response for your `serverAuthUrl` webhook.\n *\n * - Without `credentials`: returns `{ allowed: true, client_id, ... }` (option A).\n * - With `credentials`: returns `{ token }` with a pre-signed token (option B).\n */\nexport function createAuthorizedServerAuthResponse(\n options: CreateAuthorizedServerAuthResponseOptions\n): ServerAuthApprovedResponse {\n if (!isValidClientId(options.clientId)) {\n throw new Error('clientId must be 1-128 alphanumeric characters')\n }\n\n const capabilities = normalizeCapabilities(options.capabilities)\n const ttl = resolveTokenTtl(options.ttl)\n\n if (options.credentials) {\n const exp = Math.floor(Date.now() / 1000) + ttl\n return {\n token: buildConnectionToken(options.credentials.appKey, options.credentials.secret, {\n clientId: options.clientId,\n exp,\n capabilities,\n }),\n }\n }\n\n return {\n allowed: true,\n client_id: options.clientId,\n capabilities,\n ttl,\n }\n}\n\n/** Build a denied response for your `serverAuthUrl` webhook. */\nexport function createDeniedServerAuthResponse(reason?: string): ServerAuthDeniedResponse {\n return reason ? { allowed: false, reason } : { allowed: false }\n}\n\n/**\n * Authenticate an incoming `serverAuthUrl` request and return the webhook response\n * body Ark Notify expects.\n */\nexport async function handleServerAuth(\n options: HandleServerAuthOptions\n): Promise<ServerAuthResponse> {\n const decision: ServerAuthDecision = await options.isAuthorized(options.request)\n\n if (decision === false) {\n return createDeniedServerAuthResponse()\n }\n\n const clientId = decision.clientId ?? options.request.client_id\n if (!isValidClientId(clientId)) {\n return createDeniedServerAuthResponse('invalid_client_id')\n }\n\n try {\n return createAuthorizedServerAuthResponse({\n clientId,\n capabilities: decision.capabilities ?? options.request.capabilities ?? undefined,\n ttl: decision.ttl ?? options.request.ttl ?? undefined,\n credentials: options.credentials,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : 'invalid_request'\n return createDeniedServerAuthResponse(message)\n }\n}\n","export const DEFAULT_BASE_URL = 'https://ark-notify-933303906015.europe-north1.run.app'\n\nlet configuredBaseUrl: string | undefined\n\nexport interface ArkNotifyGlobalConfig {\n baseUrl?: string\n}\n\n/** Set the default base URL once at application startup. */\nexport function configureArkNotify(config: ArkNotifyGlobalConfig): void {\n if (config.baseUrl !== undefined) {\n configuredBaseUrl = config.baseUrl\n }\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return (baseUrl ?? configuredBaseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n}\n","import type { ApiError } from './types'\n\nexport class ArkNotifyError extends Error {\n readonly status: number\n readonly code: string\n readonly retryAfterSec?: number\n readonly reason?: string\n\n constructor(status: number, body: ApiError) {\n super(body.message)\n this.name = 'ArkNotifyError'\n this.status = status\n this.code = body.error\n this.retryAfterSec = body.retryAfterSec\n this.reason = body.reason\n }\n}\n\nexport function toWebSocketUrl(baseUrl: string, path: string): string {\n const normalizedBase = baseUrl.replace(/\\/$/, '')\n const normalizedPath = path.startsWith('/') ? path : `/${path}`\n const httpUrl = `${normalizedBase}${normalizedPath}`\n // Avoid URL.protocol — not supported in React Native and some runtimes.\n return httpUrl.replace(/^https:/i, 'wss:').replace(/^http:/i, 'ws:')\n}\n\n/** Append query params without URLSearchParams (unsupported in React Native). */\nexport function appendQueryParams(\n url: string,\n params: Record<string, string | undefined | null | false>\n): string {\n const query = Object.entries(params)\n .filter((entry): entry is [string, string] => {\n const value = entry[1]\n return value !== undefined && value !== null && value !== false && value !== ''\n })\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join('&')\n\n if (!query) return url\n\n const separator = url.includes('?') ? '&' : '?'\n return `${url}${separator}${query}`\n}\n\nexport function resolveValue<T>(value: T | (() => T)): T {\n return typeof value === 'function' ? (value as () => T)() : value\n}\n\nexport function isPrivateChannel(channel: string): boolean {\n return channel.startsWith('private-')\n}\n","import { resolveBaseUrl } from './config'\nimport type {\n AppCredentials,\n ArkNotifyServerStreamConfig,\n EventMessage,\n PresenceMessage,\n ServerErrorMessage,\n ServerStreamConnectedMessage,\n} from './types'\nimport { appendQueryParams, ArkNotifyError } from './utils'\n\ntype ServerStreamEventMap = {\n connected: (message: ServerStreamConnectedMessage) => void\n event: (message: EventMessage) => void\n presence: (message: PresenceMessage) => void\n message: (message: ServerStreamMessage) => void\n error: (error: Error) => void\n close: () => void\n}\n\ntype ServerStreamEventName = keyof ServerStreamEventMap\n\nfunction buildAppCredentialsHeaders(credentials: AppCredentials): Record<string, string> {\n return {\n 'X-App-Key': credentials.appKey,\n 'X-App-Secret': credentials.secret,\n }\n}\n\ntype ServerStreamMessage = ServerStreamConnectedMessage | EventMessage | PresenceMessage | ServerErrorMessage\n\nfunction dispatchSseEvent(\n data: string,\n handlers: {\n onConnected: (message: ServerStreamConnectedMessage) => void\n onEvent: (message: EventMessage) => void\n onPresence: (message: PresenceMessage) => void\n onMessage: (message: ServerStreamMessage) => void\n }\n): void {\n if (!data) return\n\n let parsed: ServerStreamMessage\n try {\n parsed = JSON.parse(data) as ServerStreamMessage\n } catch {\n return\n }\n\n if (parsed.type === 'connected') {\n handlers.onConnected(parsed)\n handlers.onMessage(parsed)\n return\n }\n\n if (parsed.type === 'event') {\n handlers.onEvent(parsed)\n handlers.onMessage(parsed)\n return\n }\n\n if (parsed.type === 'presence') {\n handlers.onPresence(parsed)\n handlers.onMessage(parsed)\n return\n }\n\n handlers.onMessage(parsed)\n}\n\nasync function readSseStream(\n body: ReadableStream<Uint8Array>,\n onSseData: (data: string) => void,\n signal: AbortSignal\n): Promise<void> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let currentEvent: string | null = null\n let dataLines: string[] = []\n\n const flushEvent = () => {\n if (dataLines.length === 0 && currentEvent === null) return\n onSseData(dataLines.join('\\n'))\n currentEvent = null\n dataLines = []\n }\n\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const rawLine of lines) {\n const line = rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine\n\n if (line === '') {\n flushEvent()\n continue\n }\n\n if (line.startsWith(':')) continue\n\n if (line.startsWith('event:')) {\n currentEvent = line.slice(6).trimStart()\n continue\n }\n\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).trimStart())\n }\n }\n }\n\n if (buffer.length > 0) {\n const line = buffer.endsWith('\\r') ? buffer.slice(0, -1) : buffer\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).trimStart())\n }\n }\n\n flushEvent()\n } finally {\n reader.releaseLock()\n }\n}\n\nexport class ArkNotifyServerStream {\n private readonly config: ArkNotifyServerStreamConfig & { baseUrl: string }\n private readonly fetchFn: typeof fetch\n private readonly listeners = new Map<\n ServerStreamEventName,\n Set<ServerStreamEventMap[ServerStreamEventName]>\n >()\n private abortController: AbortController | null = null\n private connectionId: string | null = null\n private connecting = false\n\n constructor(config: ArkNotifyServerStreamConfig) {\n this.config = { ...config, baseUrl: resolveBaseUrl(config.baseUrl) }\n this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis)\n }\n\n getConnectionId(): string | null {\n return this.connectionId\n }\n\n on<E extends ServerStreamEventName>(event: E, handler: ServerStreamEventMap[E]): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(handler)\n return () => this.off(event, handler)\n }\n\n off<E extends ServerStreamEventName>(event: E, handler: ServerStreamEventMap[E]): void {\n this.listeners.get(event)?.delete(handler)\n }\n\n private emit<E extends ServerStreamEventName>(\n event: E,\n ...args: Parameters<ServerStreamEventMap[E]>\n ): void {\n for (const handler of this.listeners.get(event) ?? []) {\n ;(handler as (...a: Parameters<ServerStreamEventMap[E]>) => void)(...args)\n }\n }\n\n async connect(): Promise<void> {\n if (this.abortController || this.connecting) return\n\n this.connecting = true\n const abortController = new AbortController()\n this.abortController = abortController\n\n const base = this.config.baseUrl.replace(/\\/$/, '')\n const streamUrl = appendQueryParams(\n `${base}/api/v1/apps/${this.config.appKey}/server-stream`,\n {\n channels: this.config.channels.join(','),\n history: this.config.history ? 'true' : undefined,\n }\n )\n\n try {\n const response = await this.fetchFn(streamUrl, {\n method: 'GET',\n headers: {\n Accept: 'text/event-stream',\n ...buildAppCredentialsHeaders(this.config.credentials),\n },\n signal: abortController.signal,\n })\n\n if (!response.ok) {\n let body: { error: string; message: string; reason?: string; retryAfterSec?: number }\n try {\n body = await response.json()\n } catch {\n body = { error: 'request_failed', message: response.statusText }\n }\n throw new ArkNotifyError(response.status, body)\n }\n\n if (!response.body) {\n throw new Error('Server stream response has no body')\n }\n\n void this.consumeStream(response.body, abortController)\n } catch (err) {\n if (abortController.signal.aborted) return\n this.abortController = null\n this.emit('error', err instanceof Error ? err : new Error(String(err)))\n this.emit('close')\n } finally {\n this.connecting = false\n }\n }\n\n private async consumeStream(body: ReadableStream<Uint8Array>, abortController: AbortController): Promise<void> {\n try {\n await readSseStream(\n body,\n (data) => {\n dispatchSseEvent(data, {\n onConnected: (message) => {\n this.connectionId = message.connection_id\n this.emit('connected', message)\n },\n onEvent: (message) => this.emit('event', message),\n onPresence: (message) => this.emit('presence', message),\n onMessage: (message) => this.emit('message', message),\n })\n },\n abortController.signal\n )\n } catch (err) {\n if (abortController.signal.aborted) return\n this.emit('error', err instanceof Error ? err : new Error(String(err)))\n } finally {\n if (this.abortController === abortController) {\n this.abortController = null\n this.connectionId = null\n this.emit('close')\n }\n }\n }\n\n disconnect(): void {\n if (!this.abortController) return\n this.abortController.abort()\n }\n\n bind(\n channel: string,\n event: string,\n handler: (data: unknown, message: EventMessage) => void\n ): () => void {\n const listener = (message: EventMessage) => {\n if (message.channel === channel && message.event === event) {\n handler(message.data, message)\n }\n }\n return this.on('event', listener)\n }\n\n bindAll(channel: string, handler: (data: unknown, message: EventMessage) => void): () => void {\n const listener = (message: EventMessage) => {\n if (message.channel === channel) {\n handler(message.data, message)\n }\n }\n return this.on('event', listener)\n }\n}\n"]}
|
package/dist/server/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { S as ServerAuthAllowedResponse,
|
|
1
|
+
import { n as ConnectionCapabilities, q as CreateAuthorizedServerAuthResponseOptions, F as ServerAuthApprovedResponse, I as ServerAuthDeniedResponse, H as HandleServerAuthOptions, K as ServerAuthResponse, J as ServerAuthRequest } from '../types-Aj_RUX12.cjs';
|
|
2
|
+
export { S as ServerAuthAllowedResponse, G as ServerAuthDecision, M as ServerAuthTokenResponse } from '../types-Aj_RUX12.cjs';
|
|
3
|
+
export { A as ArkNotifyServerStream } from '../server-stream-BK0sRKrW.cjs';
|
|
3
4
|
|
|
4
5
|
declare function isValidClientId(clientId: unknown): clientId is string;
|
|
5
6
|
/** Build a signed connection token in Ark Notify format. */
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { S as ServerAuthAllowedResponse,
|
|
1
|
+
import { n as ConnectionCapabilities, q as CreateAuthorizedServerAuthResponseOptions, F as ServerAuthApprovedResponse, I as ServerAuthDeniedResponse, H as HandleServerAuthOptions, K as ServerAuthResponse, J as ServerAuthRequest } from '../types-Aj_RUX12.js';
|
|
2
|
+
export { S as ServerAuthAllowedResponse, G as ServerAuthDecision, M as ServerAuthTokenResponse } from '../types-Aj_RUX12.js';
|
|
3
|
+
export { A as ArkNotifyServerStream } from '../server-stream-Cqe0hzxt.js';
|
|
3
4
|
|
|
4
5
|
declare function isValidClientId(clientId: unknown): clientId is string;
|
|
5
6
|
/** Build a signed connection token in Ark Notify format. */
|
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHmac } from 'crypto';
|
|
2
2
|
|
|
3
3
|
// src/server-auth.ts
|
|
4
|
-
var CLIENT_ID_PATTERN = /^[a-zA-Z0-9_
|
|
4
|
+
var CLIENT_ID_PATTERN = /^[a-zA-Z0-9_\-.@\+]{1,128}$/;
|
|
5
5
|
var DEFAULT_TTL_SECONDS = 3600;
|
|
6
6
|
var MAX_TTL_SECONDS = 86400;
|
|
7
7
|
var DEFAULT_CAPABILITIES = {
|
|
@@ -116,6 +116,233 @@ async function handleServerAuth(options) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
// src/config.ts
|
|
120
|
+
var DEFAULT_BASE_URL = "https://ark-notify-933303906015.europe-north1.run.app";
|
|
121
|
+
var configuredBaseUrl;
|
|
122
|
+
function resolveBaseUrl(baseUrl) {
|
|
123
|
+
return (baseUrl ?? configuredBaseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/utils.ts
|
|
127
|
+
var ArkNotifyError = class extends Error {
|
|
128
|
+
constructor(status, body) {
|
|
129
|
+
super(body.message);
|
|
130
|
+
this.name = "ArkNotifyError";
|
|
131
|
+
this.status = status;
|
|
132
|
+
this.code = body.error;
|
|
133
|
+
this.retryAfterSec = body.retryAfterSec;
|
|
134
|
+
this.reason = body.reason;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
function appendQueryParams(url, params) {
|
|
138
|
+
const query = Object.entries(params).filter((entry) => {
|
|
139
|
+
const value = entry[1];
|
|
140
|
+
return value !== void 0 && value !== null && value !== false && value !== "";
|
|
141
|
+
}).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
142
|
+
if (!query) return url;
|
|
143
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
144
|
+
return `${url}${separator}${query}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/server-stream.ts
|
|
148
|
+
function buildAppCredentialsHeaders(credentials) {
|
|
149
|
+
return {
|
|
150
|
+
"X-App-Key": credentials.appKey,
|
|
151
|
+
"X-App-Secret": credentials.secret
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function dispatchSseEvent(data, handlers) {
|
|
155
|
+
if (!data) return;
|
|
156
|
+
let parsed;
|
|
157
|
+
try {
|
|
158
|
+
parsed = JSON.parse(data);
|
|
159
|
+
} catch {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (parsed.type === "connected") {
|
|
163
|
+
handlers.onConnected(parsed);
|
|
164
|
+
handlers.onMessage(parsed);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (parsed.type === "event") {
|
|
168
|
+
handlers.onEvent(parsed);
|
|
169
|
+
handlers.onMessage(parsed);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (parsed.type === "presence") {
|
|
173
|
+
handlers.onPresence(parsed);
|
|
174
|
+
handlers.onMessage(parsed);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
handlers.onMessage(parsed);
|
|
178
|
+
}
|
|
179
|
+
async function readSseStream(body, onSseData, signal) {
|
|
180
|
+
const reader = body.getReader();
|
|
181
|
+
const decoder = new TextDecoder();
|
|
182
|
+
let buffer = "";
|
|
183
|
+
let currentEvent = null;
|
|
184
|
+
let dataLines = [];
|
|
185
|
+
const flushEvent = () => {
|
|
186
|
+
if (dataLines.length === 0 && currentEvent === null) return;
|
|
187
|
+
onSseData(dataLines.join("\n"));
|
|
188
|
+
currentEvent = null;
|
|
189
|
+
dataLines = [];
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
while (!signal.aborted) {
|
|
193
|
+
const { done, value } = await reader.read();
|
|
194
|
+
if (done) break;
|
|
195
|
+
buffer += decoder.decode(value, { stream: true });
|
|
196
|
+
const lines = buffer.split("\n");
|
|
197
|
+
buffer = lines.pop() ?? "";
|
|
198
|
+
for (const rawLine of lines) {
|
|
199
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
200
|
+
if (line === "") {
|
|
201
|
+
flushEvent();
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (line.startsWith(":")) continue;
|
|
205
|
+
if (line.startsWith("event:")) {
|
|
206
|
+
currentEvent = line.slice(6).trimStart();
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (line.startsWith("data:")) {
|
|
210
|
+
dataLines.push(line.slice(5).trimStart());
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (buffer.length > 0) {
|
|
215
|
+
const line = buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer;
|
|
216
|
+
if (line.startsWith("data:")) {
|
|
217
|
+
dataLines.push(line.slice(5).trimStart());
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
flushEvent();
|
|
221
|
+
} finally {
|
|
222
|
+
reader.releaseLock();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
var ArkNotifyServerStream = class {
|
|
226
|
+
constructor(config) {
|
|
227
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
228
|
+
this.abortController = null;
|
|
229
|
+
this.connectionId = null;
|
|
230
|
+
this.connecting = false;
|
|
231
|
+
this.config = { ...config, baseUrl: resolveBaseUrl(config.baseUrl) };
|
|
232
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
233
|
+
}
|
|
234
|
+
getConnectionId() {
|
|
235
|
+
return this.connectionId;
|
|
236
|
+
}
|
|
237
|
+
on(event, handler) {
|
|
238
|
+
if (!this.listeners.has(event)) {
|
|
239
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
240
|
+
}
|
|
241
|
+
this.listeners.get(event).add(handler);
|
|
242
|
+
return () => this.off(event, handler);
|
|
243
|
+
}
|
|
244
|
+
off(event, handler) {
|
|
245
|
+
this.listeners.get(event)?.delete(handler);
|
|
246
|
+
}
|
|
247
|
+
emit(event, ...args) {
|
|
248
|
+
for (const handler of this.listeners.get(event) ?? []) {
|
|
249
|
+
handler(...args);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async connect() {
|
|
253
|
+
if (this.abortController || this.connecting) return;
|
|
254
|
+
this.connecting = true;
|
|
255
|
+
const abortController = new AbortController();
|
|
256
|
+
this.abortController = abortController;
|
|
257
|
+
const base = this.config.baseUrl.replace(/\/$/, "");
|
|
258
|
+
const streamUrl = appendQueryParams(
|
|
259
|
+
`${base}/api/v1/apps/${this.config.appKey}/server-stream`,
|
|
260
|
+
{
|
|
261
|
+
channels: this.config.channels.join(","),
|
|
262
|
+
history: this.config.history ? "true" : void 0
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
try {
|
|
266
|
+
const response = await this.fetchFn(streamUrl, {
|
|
267
|
+
method: "GET",
|
|
268
|
+
headers: {
|
|
269
|
+
Accept: "text/event-stream",
|
|
270
|
+
...buildAppCredentialsHeaders(this.config.credentials)
|
|
271
|
+
},
|
|
272
|
+
signal: abortController.signal
|
|
273
|
+
});
|
|
274
|
+
if (!response.ok) {
|
|
275
|
+
let body;
|
|
276
|
+
try {
|
|
277
|
+
body = await response.json();
|
|
278
|
+
} catch {
|
|
279
|
+
body = { error: "request_failed", message: response.statusText };
|
|
280
|
+
}
|
|
281
|
+
throw new ArkNotifyError(response.status, body);
|
|
282
|
+
}
|
|
283
|
+
if (!response.body) {
|
|
284
|
+
throw new Error("Server stream response has no body");
|
|
285
|
+
}
|
|
286
|
+
void this.consumeStream(response.body, abortController);
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (abortController.signal.aborted) return;
|
|
289
|
+
this.abortController = null;
|
|
290
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
291
|
+
this.emit("close");
|
|
292
|
+
} finally {
|
|
293
|
+
this.connecting = false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async consumeStream(body, abortController) {
|
|
297
|
+
try {
|
|
298
|
+
await readSseStream(
|
|
299
|
+
body,
|
|
300
|
+
(data) => {
|
|
301
|
+
dispatchSseEvent(data, {
|
|
302
|
+
onConnected: (message) => {
|
|
303
|
+
this.connectionId = message.connection_id;
|
|
304
|
+
this.emit("connected", message);
|
|
305
|
+
},
|
|
306
|
+
onEvent: (message) => this.emit("event", message),
|
|
307
|
+
onPresence: (message) => this.emit("presence", message),
|
|
308
|
+
onMessage: (message) => this.emit("message", message)
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
abortController.signal
|
|
312
|
+
);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (abortController.signal.aborted) return;
|
|
315
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
316
|
+
} finally {
|
|
317
|
+
if (this.abortController === abortController) {
|
|
318
|
+
this.abortController = null;
|
|
319
|
+
this.connectionId = null;
|
|
320
|
+
this.emit("close");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
disconnect() {
|
|
325
|
+
if (!this.abortController) return;
|
|
326
|
+
this.abortController.abort();
|
|
327
|
+
}
|
|
328
|
+
bind(channel, event, handler) {
|
|
329
|
+
const listener = (message) => {
|
|
330
|
+
if (message.channel === channel && message.event === event) {
|
|
331
|
+
handler(message.data, message);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
return this.on("event", listener);
|
|
335
|
+
}
|
|
336
|
+
bindAll(channel, handler) {
|
|
337
|
+
const listener = (message) => {
|
|
338
|
+
if (message.channel === channel) {
|
|
339
|
+
handler(message.data, message);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
return this.on("event", listener);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export { ArkNotifyServerStream, buildConnectionToken, createAuthorizedServerAuthResponse, createDeniedServerAuthResponse, handleServerAuth, isValidClientId, parseServerAuthRequest };
|
|
120
347
|
//# sourceMappingURL=index.js.map
|
|
121
348
|
//# sourceMappingURL=index.js.map
|