@rei-standard/amsg-client 2.8.0 → 2.9.0-next.1

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 CHANGED
@@ -97,6 +97,7 @@ var ReiClient = class {
97
97
  this._userKey = null;
98
98
  this._instantEncryption = instantEncryption;
99
99
  this._instantClientToken = typeof config.instantClientToken === "string" && config.instantClientToken ? config.instantClientToken : "";
100
+ this._serverToken = typeof config.serverToken === "string" && config.serverToken ? config.serverToken : "";
100
101
  this._maxPayloadBytes = normalizeMaxPayloadBytes(config.maxPayloadBytes);
101
102
  this._lowLevelWarned = /* @__PURE__ */ new Set();
102
103
  }
@@ -110,6 +111,17 @@ var ReiClient = class {
110
111
  _resolveBaseUrl(endpointName) {
111
112
  return this._customBaseUrls[endpointName] || this._baseUrl;
112
113
  }
114
+ /**
115
+ * Attach the single-user shared secret to amsg-server endpoint requests.
116
+ * Never applied to the instant path (that uses instantClientToken).
117
+ * @private
118
+ * @param {Record<string, string>} headers
119
+ * @returns {Record<string, string>}
120
+ */
121
+ _withServerToken(headers) {
122
+ if (this._serverToken) headers["X-Client-Token"] = this._serverToken;
123
+ return headers;
124
+ }
113
125
  // ─── Initialisation ─────────────────────────────────────────────
114
126
  /**
115
127
  * Fetch the user-specific encryption key.
@@ -128,7 +140,7 @@ var ReiClient = class {
128
140
  }
129
141
  const res = await fetch(`${this._baseUrl}/get-user-key`, {
130
142
  method: "GET",
131
- headers: { "X-User-Id": this._userId }
143
+ headers: this._withServerToken({ "X-User-Id": this._userId })
132
144
  });
133
145
  const json = await res.json();
134
146
  if (!json.success) throw new Error(json.error?.message || "Failed to fetch user key");
@@ -138,6 +150,26 @@ var ReiClient = class {
138
150
  }
139
151
  this._userKey = this._hexToUint8Array(userKey);
140
152
  }
153
+ /**
154
+ * Fetch the amsg-server worker's own VAPID public key.
155
+ *
156
+ * A browser needs this as `applicationServerKey` when creating a Web Push
157
+ * subscription. Each self-hosted worker owns its VAPID keypair, so pull the
158
+ * key at runtime rather than baking it into the frontend. Sends
159
+ * `X-Client-Token` when a `serverToken` is configured.
160
+ *
161
+ * @returns {Promise<string>} The base64url VAPID public key.
162
+ * @throws {Error} When the worker has no VAPID public key configured (503).
163
+ */
164
+ async getVapidPublicKey() {
165
+ const res = await fetch(`${this._baseUrl}/vapid-public-key`, {
166
+ method: "GET",
167
+ headers: this._withServerToken({})
168
+ });
169
+ const json = await res.json();
170
+ if (!json.success) throw new Error(json.error?.message || "Failed to fetch VAPID public key");
171
+ return json.publicKey;
172
+ }
141
173
  // ─── Public API ─────────────────────────────────────────────────
142
174
  /**
143
175
  * Schedule a message.
@@ -165,12 +197,12 @@ var ReiClient = class {
165
197
  const encrypted = await this._encrypt(json);
166
198
  const res = await fetch(`${this._baseUrl}/schedule-message`, {
167
199
  method: "POST",
168
- headers: {
200
+ headers: this._withServerToken({
169
201
  "Content-Type": "application/json",
170
202
  "X-User-Id": this._userId,
171
203
  "X-Payload-Encrypted": "true",
172
204
  "X-Encryption-Version": "1"
173
- },
205
+ }),
174
206
  body: JSON.stringify(encrypted)
175
207
  });
176
208
  return res.json();
@@ -508,12 +540,12 @@ var ReiClient = class {
508
540
  const encrypted = await this._encrypt(json);
509
541
  const res = await fetch(`${this._baseUrl}/update-message?id=${encodeURIComponent(uuid)}`, {
510
542
  method: "PUT",
511
- headers: {
543
+ headers: this._withServerToken({
512
544
  "Content-Type": "application/json",
513
545
  "X-User-Id": this._userId,
514
546
  "X-Payload-Encrypted": "true",
515
547
  "X-Encryption-Version": "1"
516
- },
548
+ }),
517
549
  body: JSON.stringify(encrypted)
518
550
  });
519
551
  return res.json();
@@ -527,7 +559,7 @@ var ReiClient = class {
527
559
  async cancelMessage(uuid) {
528
560
  const res = await fetch(`${this._baseUrl}/cancel-message?id=${encodeURIComponent(uuid)}`, {
529
561
  method: "DELETE",
530
- headers: { "X-User-Id": this._userId }
562
+ headers: this._withServerToken({ "X-User-Id": this._userId })
531
563
  });
532
564
  return res.json();
533
565
  }
@@ -549,11 +581,11 @@ var ReiClient = class {
549
581
  const url = `${this._baseUrl}/messages${qs ? "?" + qs : ""}`;
550
582
  const res = await fetch(url, {
551
583
  method: "GET",
552
- headers: {
584
+ headers: this._withServerToken({
553
585
  "X-User-Id": this._userId,
554
586
  "X-Response-Encrypted": "true",
555
587
  "X-Encryption-Version": "1"
556
- }
588
+ })
557
589
  });
558
590
  const json = await res.json();
559
591
  if (!json?.success || json?.encrypted !== true) return json;
package/dist/index.d.cts CHANGED
@@ -75,6 +75,11 @@ const TEXT_ENCODER = new TextEncoder();
75
75
  * inside any frontend bundle that uses it, so
76
76
  * devtools can read it. Use for casual URL-direct
77
77
  * abuse only.
78
+ * @property {string} [serverToken] - Optional shared secret for a single-user amsg-server.
79
+ * When set, sent as the `X-Client-Token` header on
80
+ * amsg-server endpoints (schedule / messages / update /
81
+ * cancel / user-key / init). Not applied to the instant
82
+ * path (that uses `instantClientToken`).
78
83
  * @property {number|null} [maxPayloadBytes=null] - Optional local UTF-8 byte cap for outgoing request
79
84
  * payloads before encryption. `null` / omitted means
80
85
  * no SDK-level request-size limit.
@@ -367,6 +372,10 @@ class ReiClient {
367
372
  ? config.instantClientToken
368
373
  : '';
369
374
  /** @private */
375
+ this._serverToken = typeof config.serverToken === 'string' && config.serverToken
376
+ ? config.serverToken
377
+ : '';
378
+ /** @private */
370
379
  this._maxPayloadBytes = normalizeMaxPayloadBytes(config.maxPayloadBytes);
371
380
  /**
372
381
  * Per-instance latch (set of method names already warned). The
@@ -387,6 +396,18 @@ class ReiClient {
387
396
  return this._customBaseUrls[endpointName] || this._baseUrl;
388
397
  }
389
398
 
399
+ /**
400
+ * Attach the single-user shared secret to amsg-server endpoint requests.
401
+ * Never applied to the instant path (that uses instantClientToken).
402
+ * @private
403
+ * @param {Record<string, string>} headers
404
+ * @returns {Record<string, string>}
405
+ */
406
+ _withServerToken(headers) {
407
+ if (this._serverToken) headers['X-Client-Token'] = this._serverToken;
408
+ return headers;
409
+ }
410
+
390
411
  // ─── Initialisation ─────────────────────────────────────────────
391
412
 
392
413
  /**
@@ -407,7 +428,7 @@ class ReiClient {
407
428
 
408
429
  const res = await fetch(`${this._baseUrl}/get-user-key`, {
409
430
  method: 'GET',
410
- headers: { 'X-User-Id': this._userId }
431
+ headers: this._withServerToken({ 'X-User-Id': this._userId })
411
432
  });
412
433
 
413
434
  const json = await res.json();
@@ -421,6 +442,28 @@ class ReiClient {
421
442
  this._userKey = this._hexToUint8Array(userKey);
422
443
  }
423
444
 
445
+ /**
446
+ * Fetch the amsg-server worker's own VAPID public key.
447
+ *
448
+ * A browser needs this as `applicationServerKey` when creating a Web Push
449
+ * subscription. Each self-hosted worker owns its VAPID keypair, so pull the
450
+ * key at runtime rather than baking it into the frontend. Sends
451
+ * `X-Client-Token` when a `serverToken` is configured.
452
+ *
453
+ * @returns {Promise<string>} The base64url VAPID public key.
454
+ * @throws {Error} When the worker has no VAPID public key configured (503).
455
+ */
456
+ async getVapidPublicKey() {
457
+ const res = await fetch(`${this._baseUrl}/vapid-public-key`, {
458
+ method: 'GET',
459
+ headers: this._withServerToken({})
460
+ });
461
+
462
+ const json = await res.json();
463
+ if (!json.success) throw new Error(json.error?.message || 'Failed to fetch VAPID public key');
464
+ return json.publicKey;
465
+ }
466
+
424
467
  // ─── Public API ─────────────────────────────────────────────────
425
468
 
426
469
  /**
@@ -450,12 +493,12 @@ class ReiClient {
450
493
 
451
494
  const res = await fetch(`${this._baseUrl}/schedule-message`, {
452
495
  method: 'POST',
453
- headers: {
496
+ headers: this._withServerToken({
454
497
  'Content-Type': 'application/json',
455
498
  'X-User-Id': this._userId,
456
499
  'X-Payload-Encrypted': 'true',
457
500
  'X-Encryption-Version': '1'
458
- },
501
+ }),
459
502
  body: JSON.stringify(encrypted)
460
503
  });
461
504
 
@@ -883,12 +926,12 @@ class ReiClient {
883
926
 
884
927
  const res = await fetch(`${this._baseUrl}/update-message?id=${encodeURIComponent(uuid)}`, {
885
928
  method: 'PUT',
886
- headers: {
929
+ headers: this._withServerToken({
887
930
  'Content-Type': 'application/json',
888
931
  'X-User-Id': this._userId,
889
932
  'X-Payload-Encrypted': 'true',
890
933
  'X-Encryption-Version': '1'
891
- },
934
+ }),
892
935
  body: JSON.stringify(encrypted)
893
936
  });
894
937
 
@@ -904,7 +947,7 @@ class ReiClient {
904
947
  async cancelMessage(uuid) {
905
948
  const res = await fetch(`${this._baseUrl}/cancel-message?id=${encodeURIComponent(uuid)}`, {
906
949
  method: 'DELETE',
907
- headers: { 'X-User-Id': this._userId }
950
+ headers: this._withServerToken({ 'X-User-Id': this._userId })
908
951
  });
909
952
 
910
953
  return res.json();
@@ -930,11 +973,11 @@ class ReiClient {
930
973
 
931
974
  const res = await fetch(url, {
932
975
  method: 'GET',
933
- headers: {
976
+ headers: this._withServerToken({
934
977
  'X-User-Id': this._userId,
935
978
  'X-Response-Encrypted': 'true',
936
979
  'X-Encryption-Version': '1'
937
- }
980
+ })
938
981
  });
939
982
 
940
983
  const json = await res.json();
package/dist/index.d.ts CHANGED
@@ -75,6 +75,11 @@ const TEXT_ENCODER = new TextEncoder();
75
75
  * inside any frontend bundle that uses it, so
76
76
  * devtools can read it. Use for casual URL-direct
77
77
  * abuse only.
78
+ * @property {string} [serverToken] - Optional shared secret for a single-user amsg-server.
79
+ * When set, sent as the `X-Client-Token` header on
80
+ * amsg-server endpoints (schedule / messages / update /
81
+ * cancel / user-key / init). Not applied to the instant
82
+ * path (that uses `instantClientToken`).
78
83
  * @property {number|null} [maxPayloadBytes=null] - Optional local UTF-8 byte cap for outgoing request
79
84
  * payloads before encryption. `null` / omitted means
80
85
  * no SDK-level request-size limit.
@@ -367,6 +372,10 @@ class ReiClient {
367
372
  ? config.instantClientToken
368
373
  : '';
369
374
  /** @private */
375
+ this._serverToken = typeof config.serverToken === 'string' && config.serverToken
376
+ ? config.serverToken
377
+ : '';
378
+ /** @private */
370
379
  this._maxPayloadBytes = normalizeMaxPayloadBytes(config.maxPayloadBytes);
371
380
  /**
372
381
  * Per-instance latch (set of method names already warned). The
@@ -387,6 +396,18 @@ class ReiClient {
387
396
  return this._customBaseUrls[endpointName] || this._baseUrl;
388
397
  }
389
398
 
399
+ /**
400
+ * Attach the single-user shared secret to amsg-server endpoint requests.
401
+ * Never applied to the instant path (that uses instantClientToken).
402
+ * @private
403
+ * @param {Record<string, string>} headers
404
+ * @returns {Record<string, string>}
405
+ */
406
+ _withServerToken(headers) {
407
+ if (this._serverToken) headers['X-Client-Token'] = this._serverToken;
408
+ return headers;
409
+ }
410
+
390
411
  // ─── Initialisation ─────────────────────────────────────────────
391
412
 
392
413
  /**
@@ -407,7 +428,7 @@ class ReiClient {
407
428
 
408
429
  const res = await fetch(`${this._baseUrl}/get-user-key`, {
409
430
  method: 'GET',
410
- headers: { 'X-User-Id': this._userId }
431
+ headers: this._withServerToken({ 'X-User-Id': this._userId })
411
432
  });
412
433
 
413
434
  const json = await res.json();
@@ -421,6 +442,28 @@ class ReiClient {
421
442
  this._userKey = this._hexToUint8Array(userKey);
422
443
  }
423
444
 
445
+ /**
446
+ * Fetch the amsg-server worker's own VAPID public key.
447
+ *
448
+ * A browser needs this as `applicationServerKey` when creating a Web Push
449
+ * subscription. Each self-hosted worker owns its VAPID keypair, so pull the
450
+ * key at runtime rather than baking it into the frontend. Sends
451
+ * `X-Client-Token` when a `serverToken` is configured.
452
+ *
453
+ * @returns {Promise<string>} The base64url VAPID public key.
454
+ * @throws {Error} When the worker has no VAPID public key configured (503).
455
+ */
456
+ async getVapidPublicKey() {
457
+ const res = await fetch(`${this._baseUrl}/vapid-public-key`, {
458
+ method: 'GET',
459
+ headers: this._withServerToken({})
460
+ });
461
+
462
+ const json = await res.json();
463
+ if (!json.success) throw new Error(json.error?.message || 'Failed to fetch VAPID public key');
464
+ return json.publicKey;
465
+ }
466
+
424
467
  // ─── Public API ─────────────────────────────────────────────────
425
468
 
426
469
  /**
@@ -450,12 +493,12 @@ class ReiClient {
450
493
 
451
494
  const res = await fetch(`${this._baseUrl}/schedule-message`, {
452
495
  method: 'POST',
453
- headers: {
496
+ headers: this._withServerToken({
454
497
  'Content-Type': 'application/json',
455
498
  'X-User-Id': this._userId,
456
499
  'X-Payload-Encrypted': 'true',
457
500
  'X-Encryption-Version': '1'
458
- },
501
+ }),
459
502
  body: JSON.stringify(encrypted)
460
503
  });
461
504
 
@@ -883,12 +926,12 @@ class ReiClient {
883
926
 
884
927
  const res = await fetch(`${this._baseUrl}/update-message?id=${encodeURIComponent(uuid)}`, {
885
928
  method: 'PUT',
886
- headers: {
929
+ headers: this._withServerToken({
887
930
  'Content-Type': 'application/json',
888
931
  'X-User-Id': this._userId,
889
932
  'X-Payload-Encrypted': 'true',
890
933
  'X-Encryption-Version': '1'
891
- },
934
+ }),
892
935
  body: JSON.stringify(encrypted)
893
936
  });
894
937
 
@@ -904,7 +947,7 @@ class ReiClient {
904
947
  async cancelMessage(uuid) {
905
948
  const res = await fetch(`${this._baseUrl}/cancel-message?id=${encodeURIComponent(uuid)}`, {
906
949
  method: 'DELETE',
907
- headers: { 'X-User-Id': this._userId }
950
+ headers: this._withServerToken({ 'X-User-Id': this._userId })
908
951
  });
909
952
 
910
953
  return res.json();
@@ -930,11 +973,11 @@ class ReiClient {
930
973
 
931
974
  const res = await fetch(url, {
932
975
  method: 'GET',
933
- headers: {
976
+ headers: this._withServerToken({
934
977
  'X-User-Id': this._userId,
935
978
  'X-Response-Encrypted': 'true',
936
979
  'X-Encryption-Version': '1'
937
- }
980
+ })
938
981
  });
939
982
 
940
983
  const json = await res.json();
package/dist/index.mjs CHANGED
@@ -75,6 +75,7 @@ var ReiClient = class {
75
75
  this._userKey = null;
76
76
  this._instantEncryption = instantEncryption;
77
77
  this._instantClientToken = typeof config.instantClientToken === "string" && config.instantClientToken ? config.instantClientToken : "";
78
+ this._serverToken = typeof config.serverToken === "string" && config.serverToken ? config.serverToken : "";
78
79
  this._maxPayloadBytes = normalizeMaxPayloadBytes(config.maxPayloadBytes);
79
80
  this._lowLevelWarned = /* @__PURE__ */ new Set();
80
81
  }
@@ -88,6 +89,17 @@ var ReiClient = class {
88
89
  _resolveBaseUrl(endpointName) {
89
90
  return this._customBaseUrls[endpointName] || this._baseUrl;
90
91
  }
92
+ /**
93
+ * Attach the single-user shared secret to amsg-server endpoint requests.
94
+ * Never applied to the instant path (that uses instantClientToken).
95
+ * @private
96
+ * @param {Record<string, string>} headers
97
+ * @returns {Record<string, string>}
98
+ */
99
+ _withServerToken(headers) {
100
+ if (this._serverToken) headers["X-Client-Token"] = this._serverToken;
101
+ return headers;
102
+ }
91
103
  // ─── Initialisation ─────────────────────────────────────────────
92
104
  /**
93
105
  * Fetch the user-specific encryption key.
@@ -106,7 +118,7 @@ var ReiClient = class {
106
118
  }
107
119
  const res = await fetch(`${this._baseUrl}/get-user-key`, {
108
120
  method: "GET",
109
- headers: { "X-User-Id": this._userId }
121
+ headers: this._withServerToken({ "X-User-Id": this._userId })
110
122
  });
111
123
  const json = await res.json();
112
124
  if (!json.success) throw new Error(json.error?.message || "Failed to fetch user key");
@@ -116,6 +128,26 @@ var ReiClient = class {
116
128
  }
117
129
  this._userKey = this._hexToUint8Array(userKey);
118
130
  }
131
+ /**
132
+ * Fetch the amsg-server worker's own VAPID public key.
133
+ *
134
+ * A browser needs this as `applicationServerKey` when creating a Web Push
135
+ * subscription. Each self-hosted worker owns its VAPID keypair, so pull the
136
+ * key at runtime rather than baking it into the frontend. Sends
137
+ * `X-Client-Token` when a `serverToken` is configured.
138
+ *
139
+ * @returns {Promise<string>} The base64url VAPID public key.
140
+ * @throws {Error} When the worker has no VAPID public key configured (503).
141
+ */
142
+ async getVapidPublicKey() {
143
+ const res = await fetch(`${this._baseUrl}/vapid-public-key`, {
144
+ method: "GET",
145
+ headers: this._withServerToken({})
146
+ });
147
+ const json = await res.json();
148
+ if (!json.success) throw new Error(json.error?.message || "Failed to fetch VAPID public key");
149
+ return json.publicKey;
150
+ }
119
151
  // ─── Public API ─────────────────────────────────────────────────
120
152
  /**
121
153
  * Schedule a message.
@@ -143,12 +175,12 @@ var ReiClient = class {
143
175
  const encrypted = await this._encrypt(json);
144
176
  const res = await fetch(`${this._baseUrl}/schedule-message`, {
145
177
  method: "POST",
146
- headers: {
178
+ headers: this._withServerToken({
147
179
  "Content-Type": "application/json",
148
180
  "X-User-Id": this._userId,
149
181
  "X-Payload-Encrypted": "true",
150
182
  "X-Encryption-Version": "1"
151
- },
183
+ }),
152
184
  body: JSON.stringify(encrypted)
153
185
  });
154
186
  return res.json();
@@ -486,12 +518,12 @@ var ReiClient = class {
486
518
  const encrypted = await this._encrypt(json);
487
519
  const res = await fetch(`${this._baseUrl}/update-message?id=${encodeURIComponent(uuid)}`, {
488
520
  method: "PUT",
489
- headers: {
521
+ headers: this._withServerToken({
490
522
  "Content-Type": "application/json",
491
523
  "X-User-Id": this._userId,
492
524
  "X-Payload-Encrypted": "true",
493
525
  "X-Encryption-Version": "1"
494
- },
526
+ }),
495
527
  body: JSON.stringify(encrypted)
496
528
  });
497
529
  return res.json();
@@ -505,7 +537,7 @@ var ReiClient = class {
505
537
  async cancelMessage(uuid) {
506
538
  const res = await fetch(`${this._baseUrl}/cancel-message?id=${encodeURIComponent(uuid)}`, {
507
539
  method: "DELETE",
508
- headers: { "X-User-Id": this._userId }
540
+ headers: this._withServerToken({ "X-User-Id": this._userId })
509
541
  });
510
542
  return res.json();
511
543
  }
@@ -527,11 +559,11 @@ var ReiClient = class {
527
559
  const url = `${this._baseUrl}/messages${qs ? "?" + qs : ""}`;
528
560
  const res = await fetch(url, {
529
561
  method: "GET",
530
- headers: {
562
+ headers: this._withServerToken({
531
563
  "X-User-Id": this._userId,
532
564
  "X-Response-Encrypted": "true",
533
565
  "X-Encryption-Version": "1"
534
- }
566
+ })
535
567
  });
536
568
  const json = await res.json();
537
569
  if (!json?.success || json?.encrypted !== true) return json;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-client",
3
- "version": "2.8.0",
3
+ "version": "2.9.0-next.1",
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",