@rei-standard/amsg-client 2.5.0-next.0 → 2.5.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 +31 -37
- package/dist/index.d.cts +27 -26
- package/dist/index.d.ts +27 -26
- package/dist/index.mjs +20 -26
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -19,21 +19,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// src/index.js
|
|
20
20
|
var src_exports = {};
|
|
21
21
|
__export(src_exports, {
|
|
22
|
-
MESSAGE_KIND: () =>
|
|
23
|
-
MESSAGE_TYPE: () =>
|
|
24
|
-
PUSH_SOURCE: () =>
|
|
22
|
+
MESSAGE_KIND: () => import_amsg_shared2.MESSAGE_KIND,
|
|
23
|
+
MESSAGE_TYPE: () => import_amsg_shared2.MESSAGE_TYPE,
|
|
24
|
+
PUSH_SOURCE: () => import_amsg_shared2.PUSH_SOURCE,
|
|
25
25
|
ReiClient: () => ReiClient,
|
|
26
|
-
buildContentPush: () =>
|
|
27
|
-
buildErrorPush: () =>
|
|
28
|
-
buildReasoningPush: () =>
|
|
29
|
-
buildToolRequestPush: () =>
|
|
30
|
-
isContentPush: () =>
|
|
31
|
-
isErrorPush: () =>
|
|
32
|
-
isReasoningPush: () =>
|
|
33
|
-
isToolRequestPush: () =>
|
|
26
|
+
buildContentPush: () => import_amsg_shared2.buildContentPush,
|
|
27
|
+
buildErrorPush: () => import_amsg_shared2.buildErrorPush,
|
|
28
|
+
buildReasoningPush: () => import_amsg_shared2.buildReasoningPush,
|
|
29
|
+
buildToolRequestPush: () => import_amsg_shared2.buildToolRequestPush,
|
|
30
|
+
isContentPush: () => import_amsg_shared2.isContentPush,
|
|
31
|
+
isErrorPush: () => import_amsg_shared2.isErrorPush,
|
|
32
|
+
isReasoningPush: () => import_amsg_shared2.isReasoningPush,
|
|
33
|
+
isToolRequestPush: () => import_amsg_shared2.isToolRequestPush
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(src_exports);
|
|
36
36
|
var import_amsg_shared = require("@rei-standard/amsg-shared");
|
|
37
|
+
var import_amsg_shared2 = require("@rei-standard/amsg-shared");
|
|
38
|
+
var TEXT_ENCODER = new TextEncoder();
|
|
37
39
|
var AVATAR_URL_MAX_LENGTH = 2048;
|
|
38
40
|
function makeLocalError(code, message, details) {
|
|
39
41
|
const err = new Error(`[rei-standard-amsg-client] ${message}`);
|
|
@@ -187,11 +189,11 @@ var ReiClient = class {
|
|
|
187
189
|
* @param {string} [endpointPath] - Path under the resolved base URL. Default '/instant'.
|
|
188
190
|
* @param {{ authorization?: string, expectsBackupPush?: boolean }} [opts]
|
|
189
191
|
* - `authorization`: optional auth header to forward.
|
|
190
|
-
* - `expectsBackupPush`: opt-in
|
|
191
|
-
*
|
|
192
|
-
* "200 ≠
|
|
193
|
-
* (
|
|
194
|
-
*
|
|
192
|
+
* - `expectsBackupPush`: opt-in dev reminder. Set to `true` to log a
|
|
193
|
+
* one-shot console.warn that this is a low-level transport and
|
|
194
|
+
* "HTTP 200 ≠ delivery confirmation" once the worker has backup
|
|
195
|
+
* push enabled (amsg-instant 0.9.0+ default). Default (omitted) is
|
|
196
|
+
* silent.
|
|
195
197
|
* @returns {Promise<Object>} `{ success, data?: { messagesSent, sentAt }, error? }`
|
|
196
198
|
*/
|
|
197
199
|
async sendInstant(payload, endpointPath = "/instant", opts = {}) {
|
|
@@ -201,6 +203,7 @@ var ReiClient = class {
|
|
|
201
203
|
endpointPath,
|
|
202
204
|
{ authorization: opts.authorization, methodName: "sendInstant" }
|
|
203
205
|
);
|
|
206
|
+
headers["Accept"] = "application/json";
|
|
204
207
|
const res = await fetch(url, { method: "POST", headers, body });
|
|
205
208
|
return res.json();
|
|
206
209
|
}
|
|
@@ -231,10 +234,10 @@ var ReiClient = class {
|
|
|
231
234
|
* @param {(error: unknown) => void} [options.onError]
|
|
232
235
|
* @param {() => void} [options.onDone]
|
|
233
236
|
* @param {AbortSignal} [options.signal]
|
|
234
|
-
* @param {boolean} [options.expectsBackupPush] - Opt-in
|
|
235
|
-
*
|
|
236
|
-
* "
|
|
237
|
-
*
|
|
237
|
+
* @param {boolean} [options.expectsBackupPush] - Opt-in dev reminder. Set
|
|
238
|
+
* to `true` to log a one-shot console.warn that "rejection ≠ delivery
|
|
239
|
+
* failure" once the worker has backup push enabled (amsg-instant 0.9.0+
|
|
240
|
+
* default). Default (omitted) is silent.
|
|
238
241
|
* @returns {Promise<void>}
|
|
239
242
|
*/
|
|
240
243
|
async consumeInstantStream(payload, endpointPath = "/instant", options = {}) {
|
|
@@ -551,7 +554,7 @@ var ReiClient = class {
|
|
|
551
554
|
async subscribePush(vapidPublicKey, registration) {
|
|
552
555
|
const subscription = await registration.pushManager.subscribe({
|
|
553
556
|
userVisibleOnly: true,
|
|
554
|
-
applicationServerKey:
|
|
557
|
+
applicationServerKey: (0, import_amsg_shared.base64UrlToBytes)(vapidPublicKey)
|
|
555
558
|
});
|
|
556
559
|
return subscription;
|
|
557
560
|
}
|
|
@@ -599,7 +602,7 @@ var ReiClient = class {
|
|
|
599
602
|
*/
|
|
600
603
|
_assertPayloadSize(bodyJson, methodName) {
|
|
601
604
|
if (this._maxPayloadBytes == null) return;
|
|
602
|
-
const bytes =
|
|
605
|
+
const bytes = TEXT_ENCODER.encode(bodyJson).length;
|
|
603
606
|
if (bytes > this._maxPayloadBytes) {
|
|
604
607
|
throw makeLocalError(
|
|
605
608
|
"PAYLOAD_TOO_LARGE_LOCAL",
|
|
@@ -871,10 +874,10 @@ ${piece}` : piece;
|
|
|
871
874
|
return Math.min(defaultGrace, remainingMs);
|
|
872
875
|
}
|
|
873
876
|
/**
|
|
874
|
-
* One-shot dev
|
|
875
|
-
* per call via `opts.expectsBackupPush === true
|
|
876
|
-
*
|
|
877
|
-
*
|
|
877
|
+
* One-shot dev reminder for low-level instant APIs. The warning is opt-in
|
|
878
|
+
* per call via `opts.expectsBackupPush === true` and fires at most once
|
|
879
|
+
* per ReiClient instance per method name. Default (omitted or `false`)
|
|
880
|
+
* is silent.
|
|
878
881
|
*
|
|
879
882
|
* @private
|
|
880
883
|
* @param {string} methodName
|
|
@@ -886,7 +889,7 @@ ${piece}` : piece;
|
|
|
886
889
|
this._lowLevelWarned.add(methodName);
|
|
887
890
|
const verdict = methodName === "sendInstant" ? "HTTP 200 \u2260 delivery confirmation" : "rejection \u2260 delivery failure";
|
|
888
891
|
console.warn(
|
|
889
|
-
`[rei-standard-amsg-client] ${methodName} is a low-level transport \u2014 ${verdict} when the worker is configured with always-on backup Web Push (amsg-instant 0.9.0+ default). Prefer client.deliver() for a correct delivered / cancelled / timeout / send-failed verdict
|
|
892
|
+
`[rei-standard-amsg-client] ${methodName} is a low-level transport \u2014 ${verdict} when the worker is configured with always-on backup Web Push (amsg-instant 0.9.0+ default). Prefer client.deliver() for a correct delivered / cancelled / timeout / send-failed verdict.`
|
|
890
893
|
);
|
|
891
894
|
}
|
|
892
895
|
// ─── Crypto helpers (Web Crypto API) ────────────────────────────
|
|
@@ -900,7 +903,7 @@ ${piece}` : piece;
|
|
|
900
903
|
if (!this._userKey) throw new Error("[rei-standard-amsg-client] Not initialised. Call init() first.");
|
|
901
904
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
902
905
|
const key = await crypto.subtle.importKey("raw", this._userKey, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
903
|
-
const encoded =
|
|
906
|
+
const encoded = TEXT_ENCODER.encode(plaintext);
|
|
904
907
|
const cipherBuf = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoded);
|
|
905
908
|
const cipherArr = new Uint8Array(cipherBuf);
|
|
906
909
|
const encryptedData = cipherArr.slice(0, cipherArr.length - 16);
|
|
@@ -953,15 +956,6 @@ ${piece}` : piece;
|
|
|
953
956
|
}
|
|
954
957
|
return arr;
|
|
955
958
|
}
|
|
956
|
-
/** @private */
|
|
957
|
-
_urlBase64ToUint8Array(base64String) {
|
|
958
|
-
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
959
|
-
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
960
|
-
const raw = atob(base64);
|
|
961
|
-
const arr = new Uint8Array(raw.length);
|
|
962
|
-
for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
|
|
963
|
-
return arr;
|
|
964
|
-
}
|
|
965
959
|
};
|
|
966
960
|
function normalizeMaxPayloadBytes(value) {
|
|
967
961
|
if (value === void 0 || value === null) return null;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { base64UrlToBytes } from '@rei-standard/amsg-shared';
|
|
1
2
|
export { MESSAGE_KIND, MESSAGE_TYPE, PUSH_SOURCE, buildContentPush, buildErrorPush, buildReasoningPush, buildToolRequestPush, isContentPush, isErrorPush, isReasoningPush, isToolRequestPush } from '@rei-standard/amsg-shared';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -30,6 +31,11 @@ export { MESSAGE_KIND, MESSAGE_TYPE, PUSH_SOURCE, buildContentPush, buildErrorPu
|
|
|
30
31
|
* await client.scheduleMessage({ ... });
|
|
31
32
|
*/
|
|
32
33
|
|
|
34
|
+
|
|
35
|
+
// `TextEncoder` is stateless — hoist once instead of allocating a fresh
|
|
36
|
+
// instance for every encrypt + payload-size check.
|
|
37
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
38
|
+
|
|
33
39
|
/** @typedef {import('@rei-standard/amsg-shared').MessageKind} MessageKind */
|
|
34
40
|
/** @typedef {import('@rei-standard/amsg-shared').MessageType} MessageType */
|
|
35
41
|
/** @typedef {import('@rei-standard/amsg-shared').PushSource} PushSource */
|
|
@@ -400,11 +406,11 @@ class ReiClient {
|
|
|
400
406
|
* @param {string} [endpointPath] - Path under the resolved base URL. Default '/instant'.
|
|
401
407
|
* @param {{ authorization?: string, expectsBackupPush?: boolean }} [opts]
|
|
402
408
|
* - `authorization`: optional auth header to forward.
|
|
403
|
-
* - `expectsBackupPush`: opt-in
|
|
404
|
-
*
|
|
405
|
-
* "200 ≠
|
|
406
|
-
* (
|
|
407
|
-
*
|
|
409
|
+
* - `expectsBackupPush`: opt-in dev reminder. Set to `true` to log a
|
|
410
|
+
* one-shot console.warn that this is a low-level transport and
|
|
411
|
+
* "HTTP 200 ≠ delivery confirmation" once the worker has backup
|
|
412
|
+
* push enabled (amsg-instant 0.9.0+ default). Default (omitted) is
|
|
413
|
+
* silent.
|
|
408
414
|
* @returns {Promise<Object>} `{ success, data?: { messagesSent, sentAt }, error? }`
|
|
409
415
|
*/
|
|
410
416
|
async sendInstant(payload, endpointPath = '/instant', opts = {}) {
|
|
@@ -415,6 +421,10 @@ class ReiClient {
|
|
|
415
421
|
endpointPath,
|
|
416
422
|
{ authorization: opts.authorization, methodName: 'sendInstant' }
|
|
417
423
|
);
|
|
424
|
+
// Pin the response shape: amsg-instant routes the JSON `{ success, data }`
|
|
425
|
+
// envelope only when the caller asked exclusively for it. Omitting Accept
|
|
426
|
+
// gets the SSE branch and `res.json()` then throws on the SSE bytes.
|
|
427
|
+
headers['Accept'] = 'application/json';
|
|
418
428
|
|
|
419
429
|
const res = await fetch(url, { method: 'POST', headers, body });
|
|
420
430
|
return res.json();
|
|
@@ -447,10 +457,10 @@ class ReiClient {
|
|
|
447
457
|
* @param {(error: unknown) => void} [options.onError]
|
|
448
458
|
* @param {() => void} [options.onDone]
|
|
449
459
|
* @param {AbortSignal} [options.signal]
|
|
450
|
-
* @param {boolean} [options.expectsBackupPush] - Opt-in
|
|
451
|
-
*
|
|
452
|
-
* "
|
|
453
|
-
*
|
|
460
|
+
* @param {boolean} [options.expectsBackupPush] - Opt-in dev reminder. Set
|
|
461
|
+
* to `true` to log a one-shot console.warn that "rejection ≠ delivery
|
|
462
|
+
* failure" once the worker has backup push enabled (amsg-instant 0.9.0+
|
|
463
|
+
* default). Default (omitted) is silent.
|
|
454
464
|
* @returns {Promise<void>}
|
|
455
465
|
*/
|
|
456
466
|
async consumeInstantStream(payload, endpointPath = '/instant', options = {}) {
|
|
@@ -860,7 +870,7 @@ class ReiClient {
|
|
|
860
870
|
async subscribePush(vapidPublicKey, registration) {
|
|
861
871
|
const subscription = await registration.pushManager.subscribe({
|
|
862
872
|
userVisibleOnly: true,
|
|
863
|
-
applicationServerKey:
|
|
873
|
+
applicationServerKey: base64UrlToBytes(vapidPublicKey)
|
|
864
874
|
});
|
|
865
875
|
return subscription;
|
|
866
876
|
}
|
|
@@ -911,7 +921,7 @@ class ReiClient {
|
|
|
911
921
|
*/
|
|
912
922
|
_assertPayloadSize(bodyJson, methodName) {
|
|
913
923
|
if (this._maxPayloadBytes == null) return;
|
|
914
|
-
const bytes =
|
|
924
|
+
const bytes = TEXT_ENCODER.encode(bodyJson).length;
|
|
915
925
|
if (bytes > this._maxPayloadBytes) {
|
|
916
926
|
throw makeLocalError(
|
|
917
927
|
'PAYLOAD_TOO_LARGE_LOCAL',
|
|
@@ -1204,10 +1214,10 @@ class ReiClient {
|
|
|
1204
1214
|
}
|
|
1205
1215
|
|
|
1206
1216
|
/**
|
|
1207
|
-
* One-shot dev
|
|
1208
|
-
* per call via `opts.expectsBackupPush === true
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1217
|
+
* One-shot dev reminder for low-level instant APIs. The warning is opt-in
|
|
1218
|
+
* per call via `opts.expectsBackupPush === true` and fires at most once
|
|
1219
|
+
* per ReiClient instance per method name. Default (omitted or `false`)
|
|
1220
|
+
* is silent.
|
|
1211
1221
|
*
|
|
1212
1222
|
* @private
|
|
1213
1223
|
* @param {string} methodName
|
|
@@ -1221,7 +1231,7 @@ class ReiClient {
|
|
|
1221
1231
|
? 'HTTP 200 ≠ delivery confirmation'
|
|
1222
1232
|
: 'rejection ≠ delivery failure';
|
|
1223
1233
|
console.warn(
|
|
1224
|
-
`[rei-standard-amsg-client] ${methodName} is a low-level transport — ${verdict} when the worker is configured with always-on backup Web Push (amsg-instant 0.9.0+ default). Prefer client.deliver() for a correct delivered / cancelled / timeout / send-failed verdict
|
|
1234
|
+
`[rei-standard-amsg-client] ${methodName} is a low-level transport — ${verdict} when the worker is configured with always-on backup Web Push (amsg-instant 0.9.0+ default). Prefer client.deliver() for a correct delivered / cancelled / timeout / send-failed verdict.`
|
|
1225
1235
|
);
|
|
1226
1236
|
}
|
|
1227
1237
|
|
|
@@ -1238,7 +1248,7 @@ class ReiClient {
|
|
|
1238
1248
|
|
|
1239
1249
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1240
1250
|
const key = await crypto.subtle.importKey('raw', this._userKey, { name: 'AES-GCM' }, false, ['encrypt']);
|
|
1241
|
-
const encoded =
|
|
1251
|
+
const encoded = TEXT_ENCODER.encode(plaintext);
|
|
1242
1252
|
const cipherBuf = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded);
|
|
1243
1253
|
|
|
1244
1254
|
// Web Crypto appends the 16-byte auth tag at the end of the ciphertext
|
|
@@ -1302,15 +1312,6 @@ class ReiClient {
|
|
|
1302
1312
|
return arr;
|
|
1303
1313
|
}
|
|
1304
1314
|
|
|
1305
|
-
/** @private */
|
|
1306
|
-
_urlBase64ToUint8Array(base64String) {
|
|
1307
|
-
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
|
1308
|
-
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
|
1309
|
-
const raw = atob(base64);
|
|
1310
|
-
const arr = new Uint8Array(raw.length);
|
|
1311
|
-
for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
|
|
1312
|
-
return arr;
|
|
1313
|
-
}
|
|
1314
1315
|
}
|
|
1315
1316
|
|
|
1316
1317
|
function normalizeMaxPayloadBytes(value) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { base64UrlToBytes } from '@rei-standard/amsg-shared';
|
|
1
2
|
export { MESSAGE_KIND, MESSAGE_TYPE, PUSH_SOURCE, buildContentPush, buildErrorPush, buildReasoningPush, buildToolRequestPush, isContentPush, isErrorPush, isReasoningPush, isToolRequestPush } from '@rei-standard/amsg-shared';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -30,6 +31,11 @@ export { MESSAGE_KIND, MESSAGE_TYPE, PUSH_SOURCE, buildContentPush, buildErrorPu
|
|
|
30
31
|
* await client.scheduleMessage({ ... });
|
|
31
32
|
*/
|
|
32
33
|
|
|
34
|
+
|
|
35
|
+
// `TextEncoder` is stateless — hoist once instead of allocating a fresh
|
|
36
|
+
// instance for every encrypt + payload-size check.
|
|
37
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
38
|
+
|
|
33
39
|
/** @typedef {import('@rei-standard/amsg-shared').MessageKind} MessageKind */
|
|
34
40
|
/** @typedef {import('@rei-standard/amsg-shared').MessageType} MessageType */
|
|
35
41
|
/** @typedef {import('@rei-standard/amsg-shared').PushSource} PushSource */
|
|
@@ -400,11 +406,11 @@ class ReiClient {
|
|
|
400
406
|
* @param {string} [endpointPath] - Path under the resolved base URL. Default '/instant'.
|
|
401
407
|
* @param {{ authorization?: string, expectsBackupPush?: boolean }} [opts]
|
|
402
408
|
* - `authorization`: optional auth header to forward.
|
|
403
|
-
* - `expectsBackupPush`: opt-in
|
|
404
|
-
*
|
|
405
|
-
* "200 ≠
|
|
406
|
-
* (
|
|
407
|
-
*
|
|
409
|
+
* - `expectsBackupPush`: opt-in dev reminder. Set to `true` to log a
|
|
410
|
+
* one-shot console.warn that this is a low-level transport and
|
|
411
|
+
* "HTTP 200 ≠ delivery confirmation" once the worker has backup
|
|
412
|
+
* push enabled (amsg-instant 0.9.0+ default). Default (omitted) is
|
|
413
|
+
* silent.
|
|
408
414
|
* @returns {Promise<Object>} `{ success, data?: { messagesSent, sentAt }, error? }`
|
|
409
415
|
*/
|
|
410
416
|
async sendInstant(payload, endpointPath = '/instant', opts = {}) {
|
|
@@ -415,6 +421,10 @@ class ReiClient {
|
|
|
415
421
|
endpointPath,
|
|
416
422
|
{ authorization: opts.authorization, methodName: 'sendInstant' }
|
|
417
423
|
);
|
|
424
|
+
// Pin the response shape: amsg-instant routes the JSON `{ success, data }`
|
|
425
|
+
// envelope only when the caller asked exclusively for it. Omitting Accept
|
|
426
|
+
// gets the SSE branch and `res.json()` then throws on the SSE bytes.
|
|
427
|
+
headers['Accept'] = 'application/json';
|
|
418
428
|
|
|
419
429
|
const res = await fetch(url, { method: 'POST', headers, body });
|
|
420
430
|
return res.json();
|
|
@@ -447,10 +457,10 @@ class ReiClient {
|
|
|
447
457
|
* @param {(error: unknown) => void} [options.onError]
|
|
448
458
|
* @param {() => void} [options.onDone]
|
|
449
459
|
* @param {AbortSignal} [options.signal]
|
|
450
|
-
* @param {boolean} [options.expectsBackupPush] - Opt-in
|
|
451
|
-
*
|
|
452
|
-
* "
|
|
453
|
-
*
|
|
460
|
+
* @param {boolean} [options.expectsBackupPush] - Opt-in dev reminder. Set
|
|
461
|
+
* to `true` to log a one-shot console.warn that "rejection ≠ delivery
|
|
462
|
+
* failure" once the worker has backup push enabled (amsg-instant 0.9.0+
|
|
463
|
+
* default). Default (omitted) is silent.
|
|
454
464
|
* @returns {Promise<void>}
|
|
455
465
|
*/
|
|
456
466
|
async consumeInstantStream(payload, endpointPath = '/instant', options = {}) {
|
|
@@ -860,7 +870,7 @@ class ReiClient {
|
|
|
860
870
|
async subscribePush(vapidPublicKey, registration) {
|
|
861
871
|
const subscription = await registration.pushManager.subscribe({
|
|
862
872
|
userVisibleOnly: true,
|
|
863
|
-
applicationServerKey:
|
|
873
|
+
applicationServerKey: base64UrlToBytes(vapidPublicKey)
|
|
864
874
|
});
|
|
865
875
|
return subscription;
|
|
866
876
|
}
|
|
@@ -911,7 +921,7 @@ class ReiClient {
|
|
|
911
921
|
*/
|
|
912
922
|
_assertPayloadSize(bodyJson, methodName) {
|
|
913
923
|
if (this._maxPayloadBytes == null) return;
|
|
914
|
-
const bytes =
|
|
924
|
+
const bytes = TEXT_ENCODER.encode(bodyJson).length;
|
|
915
925
|
if (bytes > this._maxPayloadBytes) {
|
|
916
926
|
throw makeLocalError(
|
|
917
927
|
'PAYLOAD_TOO_LARGE_LOCAL',
|
|
@@ -1204,10 +1214,10 @@ class ReiClient {
|
|
|
1204
1214
|
}
|
|
1205
1215
|
|
|
1206
1216
|
/**
|
|
1207
|
-
* One-shot dev
|
|
1208
|
-
* per call via `opts.expectsBackupPush === true
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1217
|
+
* One-shot dev reminder for low-level instant APIs. The warning is opt-in
|
|
1218
|
+
* per call via `opts.expectsBackupPush === true` and fires at most once
|
|
1219
|
+
* per ReiClient instance per method name. Default (omitted or `false`)
|
|
1220
|
+
* is silent.
|
|
1211
1221
|
*
|
|
1212
1222
|
* @private
|
|
1213
1223
|
* @param {string} methodName
|
|
@@ -1221,7 +1231,7 @@ class ReiClient {
|
|
|
1221
1231
|
? 'HTTP 200 ≠ delivery confirmation'
|
|
1222
1232
|
: 'rejection ≠ delivery failure';
|
|
1223
1233
|
console.warn(
|
|
1224
|
-
`[rei-standard-amsg-client] ${methodName} is a low-level transport — ${verdict} when the worker is configured with always-on backup Web Push (amsg-instant 0.9.0+ default). Prefer client.deliver() for a correct delivered / cancelled / timeout / send-failed verdict
|
|
1234
|
+
`[rei-standard-amsg-client] ${methodName} is a low-level transport — ${verdict} when the worker is configured with always-on backup Web Push (amsg-instant 0.9.0+ default). Prefer client.deliver() for a correct delivered / cancelled / timeout / send-failed verdict.`
|
|
1225
1235
|
);
|
|
1226
1236
|
}
|
|
1227
1237
|
|
|
@@ -1238,7 +1248,7 @@ class ReiClient {
|
|
|
1238
1248
|
|
|
1239
1249
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1240
1250
|
const key = await crypto.subtle.importKey('raw', this._userKey, { name: 'AES-GCM' }, false, ['encrypt']);
|
|
1241
|
-
const encoded =
|
|
1251
|
+
const encoded = TEXT_ENCODER.encode(plaintext);
|
|
1242
1252
|
const cipherBuf = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded);
|
|
1243
1253
|
|
|
1244
1254
|
// Web Crypto appends the 16-byte auth tag at the end of the ciphertext
|
|
@@ -1302,15 +1312,6 @@ class ReiClient {
|
|
|
1302
1312
|
return arr;
|
|
1303
1313
|
}
|
|
1304
1314
|
|
|
1305
|
-
/** @private */
|
|
1306
|
-
_urlBase64ToUint8Array(base64String) {
|
|
1307
|
-
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
|
1308
|
-
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
|
1309
|
-
const raw = atob(base64);
|
|
1310
|
-
const arr = new Uint8Array(raw.length);
|
|
1311
|
-
for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
|
|
1312
|
-
return arr;
|
|
1313
|
-
}
|
|
1314
1315
|
}
|
|
1315
1316
|
|
|
1316
1317
|
function normalizeMaxPayloadBytes(value) {
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.js
|
|
2
|
+
import { base64UrlToBytes } from "@rei-standard/amsg-shared";
|
|
2
3
|
import {
|
|
3
4
|
MESSAGE_KIND,
|
|
4
5
|
MESSAGE_TYPE,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
isToolRequestPush,
|
|
13
14
|
isErrorPush
|
|
14
15
|
} from "@rei-standard/amsg-shared";
|
|
16
|
+
var TEXT_ENCODER = new TextEncoder();
|
|
15
17
|
var AVATAR_URL_MAX_LENGTH = 2048;
|
|
16
18
|
function makeLocalError(code, message, details) {
|
|
17
19
|
const err = new Error(`[rei-standard-amsg-client] ${message}`);
|
|
@@ -165,11 +167,11 @@ var ReiClient = class {
|
|
|
165
167
|
* @param {string} [endpointPath] - Path under the resolved base URL. Default '/instant'.
|
|
166
168
|
* @param {{ authorization?: string, expectsBackupPush?: boolean }} [opts]
|
|
167
169
|
* - `authorization`: optional auth header to forward.
|
|
168
|
-
* - `expectsBackupPush`: opt-in
|
|
169
|
-
*
|
|
170
|
-
* "200 ≠
|
|
171
|
-
* (
|
|
172
|
-
*
|
|
170
|
+
* - `expectsBackupPush`: opt-in dev reminder. Set to `true` to log a
|
|
171
|
+
* one-shot console.warn that this is a low-level transport and
|
|
172
|
+
* "HTTP 200 ≠ delivery confirmation" once the worker has backup
|
|
173
|
+
* push enabled (amsg-instant 0.9.0+ default). Default (omitted) is
|
|
174
|
+
* silent.
|
|
173
175
|
* @returns {Promise<Object>} `{ success, data?: { messagesSent, sentAt }, error? }`
|
|
174
176
|
*/
|
|
175
177
|
async sendInstant(payload, endpointPath = "/instant", opts = {}) {
|
|
@@ -179,6 +181,7 @@ var ReiClient = class {
|
|
|
179
181
|
endpointPath,
|
|
180
182
|
{ authorization: opts.authorization, methodName: "sendInstant" }
|
|
181
183
|
);
|
|
184
|
+
headers["Accept"] = "application/json";
|
|
182
185
|
const res = await fetch(url, { method: "POST", headers, body });
|
|
183
186
|
return res.json();
|
|
184
187
|
}
|
|
@@ -209,10 +212,10 @@ var ReiClient = class {
|
|
|
209
212
|
* @param {(error: unknown) => void} [options.onError]
|
|
210
213
|
* @param {() => void} [options.onDone]
|
|
211
214
|
* @param {AbortSignal} [options.signal]
|
|
212
|
-
* @param {boolean} [options.expectsBackupPush] - Opt-in
|
|
213
|
-
*
|
|
214
|
-
* "
|
|
215
|
-
*
|
|
215
|
+
* @param {boolean} [options.expectsBackupPush] - Opt-in dev reminder. Set
|
|
216
|
+
* to `true` to log a one-shot console.warn that "rejection ≠ delivery
|
|
217
|
+
* failure" once the worker has backup push enabled (amsg-instant 0.9.0+
|
|
218
|
+
* default). Default (omitted) is silent.
|
|
216
219
|
* @returns {Promise<void>}
|
|
217
220
|
*/
|
|
218
221
|
async consumeInstantStream(payload, endpointPath = "/instant", options = {}) {
|
|
@@ -529,7 +532,7 @@ var ReiClient = class {
|
|
|
529
532
|
async subscribePush(vapidPublicKey, registration) {
|
|
530
533
|
const subscription = await registration.pushManager.subscribe({
|
|
531
534
|
userVisibleOnly: true,
|
|
532
|
-
applicationServerKey:
|
|
535
|
+
applicationServerKey: base64UrlToBytes(vapidPublicKey)
|
|
533
536
|
});
|
|
534
537
|
return subscription;
|
|
535
538
|
}
|
|
@@ -577,7 +580,7 @@ var ReiClient = class {
|
|
|
577
580
|
*/
|
|
578
581
|
_assertPayloadSize(bodyJson, methodName) {
|
|
579
582
|
if (this._maxPayloadBytes == null) return;
|
|
580
|
-
const bytes =
|
|
583
|
+
const bytes = TEXT_ENCODER.encode(bodyJson).length;
|
|
581
584
|
if (bytes > this._maxPayloadBytes) {
|
|
582
585
|
throw makeLocalError(
|
|
583
586
|
"PAYLOAD_TOO_LARGE_LOCAL",
|
|
@@ -849,10 +852,10 @@ ${piece}` : piece;
|
|
|
849
852
|
return Math.min(defaultGrace, remainingMs);
|
|
850
853
|
}
|
|
851
854
|
/**
|
|
852
|
-
* One-shot dev
|
|
853
|
-
* per call via `opts.expectsBackupPush === true
|
|
854
|
-
*
|
|
855
|
-
*
|
|
855
|
+
* One-shot dev reminder for low-level instant APIs. The warning is opt-in
|
|
856
|
+
* per call via `opts.expectsBackupPush === true` and fires at most once
|
|
857
|
+
* per ReiClient instance per method name. Default (omitted or `false`)
|
|
858
|
+
* is silent.
|
|
856
859
|
*
|
|
857
860
|
* @private
|
|
858
861
|
* @param {string} methodName
|
|
@@ -864,7 +867,7 @@ ${piece}` : piece;
|
|
|
864
867
|
this._lowLevelWarned.add(methodName);
|
|
865
868
|
const verdict = methodName === "sendInstant" ? "HTTP 200 \u2260 delivery confirmation" : "rejection \u2260 delivery failure";
|
|
866
869
|
console.warn(
|
|
867
|
-
`[rei-standard-amsg-client] ${methodName} is a low-level transport \u2014 ${verdict} when the worker is configured with always-on backup Web Push (amsg-instant 0.9.0+ default). Prefer client.deliver() for a correct delivered / cancelled / timeout / send-failed verdict
|
|
870
|
+
`[rei-standard-amsg-client] ${methodName} is a low-level transport \u2014 ${verdict} when the worker is configured with always-on backup Web Push (amsg-instant 0.9.0+ default). Prefer client.deliver() for a correct delivered / cancelled / timeout / send-failed verdict.`
|
|
868
871
|
);
|
|
869
872
|
}
|
|
870
873
|
// ─── Crypto helpers (Web Crypto API) ────────────────────────────
|
|
@@ -878,7 +881,7 @@ ${piece}` : piece;
|
|
|
878
881
|
if (!this._userKey) throw new Error("[rei-standard-amsg-client] Not initialised. Call init() first.");
|
|
879
882
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
880
883
|
const key = await crypto.subtle.importKey("raw", this._userKey, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
881
|
-
const encoded =
|
|
884
|
+
const encoded = TEXT_ENCODER.encode(plaintext);
|
|
882
885
|
const cipherBuf = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoded);
|
|
883
886
|
const cipherArr = new Uint8Array(cipherBuf);
|
|
884
887
|
const encryptedData = cipherArr.slice(0, cipherArr.length - 16);
|
|
@@ -931,15 +934,6 @@ ${piece}` : piece;
|
|
|
931
934
|
}
|
|
932
935
|
return arr;
|
|
933
936
|
}
|
|
934
|
-
/** @private */
|
|
935
|
-
_urlBase64ToUint8Array(base64String) {
|
|
936
|
-
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
937
|
-
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
938
|
-
const raw = atob(base64);
|
|
939
|
-
const arr = new Uint8Array(raw.length);
|
|
940
|
-
for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
|
|
941
|
-
return arr;
|
|
942
|
-
}
|
|
943
937
|
};
|
|
944
938
|
function normalizeMaxPayloadBytes(value) {
|
|
945
939
|
if (value === void 0 || value === null) return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rei-standard/amsg-client",
|
|
3
|
-
"version": "2.5.0
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "ReiStandard Active Messaging browser client SDK — also re-exports shared push types, builders, and guards from @rei-standard/amsg-shared",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|