@mediaviz/sdk 1.9.0-dev.106 → 1.9.0-dev.117

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/sdk.cjs CHANGED
@@ -4,36 +4,181 @@ function getDefaultExportFromCjs (x) {
4
4
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
5
5
  }
6
6
 
7
- var pkce;
8
- var hasRequiredPkce;
7
+ var base64;
8
+ var hasRequiredBase64;
9
9
 
10
- function requirePkce () {
11
- if (hasRequiredPkce) return pkce;
12
- hasRequiredPkce = 1;
10
+ function requireBase64 () {
11
+ if (hasRequiredBase64) return base64;
12
+ hasRequiredBase64 = 1;
13
13
 
14
- // base64url-encode a Uint8Array without padding
14
+ // Base64URL codec, used by PKCE (encode) and JWT payload decoding (decode).
15
+ // Hand-rolled rather than delegating to atob/btoa, Buffer, or TextDecoder:
16
+ // none of those is present on every target runtime — React Native's Hermes has
17
+ // no Buffer and does not guarantee atob — and the alphabet here is URL-safe and
18
+ // unpadded, which the standard helpers do not produce.
19
+
20
+ const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
21
+
22
+ /**
23
+ * @param {Uint8Array} bytes
24
+ * @returns {string} unpadded base64url
25
+ */
15
26
  function base64urlEncode(bytes) {
16
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
17
27
  let result = '';
18
28
  for (let i = 0; i < bytes.length; i += 3) {
19
29
  const b0 = bytes[i];
20
30
  const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
21
31
  const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
22
- result += chars[b0 >> 2];
23
- result += chars[((b0 & 3) << 4) | (b1 >> 4)];
24
- if (i + 1 < bytes.length) result += chars[((b1 & 0xf) << 2) | (b2 >> 6)];
25
- if (i + 2 < bytes.length) result += chars[b2 & 0x3f];
32
+ result += ALPHABET[b0 >> 2];
33
+ result += ALPHABET[((b0 & 3) << 4) | (b1 >> 4)];
34
+ if (i + 1 < bytes.length) result += ALPHABET[((b1 & 0xf) << 2) | (b2 >> 6)];
35
+ if (i + 2 < bytes.length) result += ALPHABET[b2 & 0x3f];
26
36
  }
27
37
  return result;
28
38
  }
29
39
 
40
+ /**
41
+ * Decodes base64url or standard base64, padded or not.
42
+ * @param {string} str
43
+ * @returns {Uint8Array}
44
+ */
45
+ function base64urlDecode(str) {
46
+ const clean = String(str).replace(/=+$/, '');
47
+ const out = new Uint8Array((clean.length * 3) >> 2);
48
+ let acc = 0;
49
+ let bits = 0;
50
+ let i = 0;
51
+ for (const ch of clean) {
52
+ const value = decodeChar(ch);
53
+ if (value < 0) throw new TypeError(`Invalid base64url character: ${JSON.stringify(ch)}`);
54
+ acc = (acc << 6) | value;
55
+ bits += 6;
56
+ if (bits >= 8) {
57
+ bits -= 8;
58
+ out[i++] = (acc >> bits) & 0xff;
59
+ }
60
+ }
61
+ return out.subarray(0, i);
62
+ }
63
+
64
+ /**
65
+ * @param {Uint8Array} bytes
66
+ * @returns {string}
67
+ */
68
+ function utf8Decode(bytes) {
69
+ let out = '';
70
+ let i = 0;
71
+ while (i < bytes.length) {
72
+ const b0 = bytes[i++];
73
+ let codePoint;
74
+ if (b0 < 0x80) codePoint = b0;
75
+ else if ((b0 & 0xe0) === 0xc0) codePoint = ((b0 & 0x1f) << 6) | (bytes[i++] & 0x3f);
76
+ else if ((b0 & 0xf0) === 0xe0)
77
+ codePoint = ((b0 & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
78
+ else
79
+ codePoint =
80
+ ((b0 & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
81
+ out += String.fromCodePoint(codePoint);
82
+ }
83
+ return out;
84
+ }
85
+
86
+ // helpers
87
+ function decodeChar(ch) {
88
+ // '+' and '/' map onto the URL-safe alphabet's '-' and '_' slots, so standard
89
+ // base64 decodes through the same table.
90
+ if (ch === '+') return 62;
91
+ if (ch === '/') return 63;
92
+ return ALPHABET.indexOf(ch);
93
+ }
94
+
95
+ base64 = { base64urlEncode, base64urlDecode, utf8Decode };
96
+ return base64;
97
+ }
98
+
99
+ var crypto;
100
+ var hasRequiredCrypto;
101
+
102
+ function requireCrypto () {
103
+ if (hasRequiredCrypto) return crypto;
104
+ hasRequiredCrypto = 1;
105
+
106
+ // Crypto seam for PKCE.
107
+ //
108
+ // Defaults to WebCrypto, which covers browsers and Node >= 18. React Native's
109
+ // Hermes runtime provides neither half of what PKCE needs: there is no
110
+ // SubtleCrypto at all (so no SHA-256), and no crypto.getRandomValues without a
111
+ // polyfill. Hosts on such runtimes call configureCrypto() once at startup with
112
+ // native-backed equivalents (expo-crypto, react-native-quick-crypto).
113
+
114
+ /** @typedef {{getRandomValues: (bytes: Uint8Array) => Uint8Array, sha256: (bytes: Uint8Array) => Promise<Uint8Array>}} CryptoProvider */
115
+
116
+ let _provider = null;
117
+
118
+ /**
119
+ * Installs a crypto provider, replacing the WebCrypto default.
120
+ * @param {CryptoProvider} provider
121
+ */
122
+ function configureCrypto(provider) {
123
+ if (!provider || typeof provider.getRandomValues !== 'function' || typeof provider.sha256 !== 'function') {
124
+ throw new TypeError(
125
+ 'configureCrypto requires { getRandomValues(bytes) => bytes, sha256(bytes) => Promise<Uint8Array> }'
126
+ );
127
+ }
128
+ _provider = provider;
129
+ }
130
+
131
+ /** Restores the WebCrypto default. Primarily for tests. */
132
+ function resetCrypto() {
133
+ _provider = null;
134
+ }
135
+
136
+ /**
137
+ * @returns {CryptoProvider}
138
+ */
139
+ function getCrypto() {
140
+ return _provider ?? webCryptoProvider();
141
+ }
142
+
143
+ // helpers
144
+ function webCryptoProvider() {
145
+ const wc = globalThis.crypto;
146
+ // Named explicitly rather than letting a property access blow up deep inside
147
+ // PKCE: on React Native the failure is a missing polyfill, and the message
148
+ // has to say so.
149
+ if (!wc || typeof wc.getRandomValues !== 'function' || !wc.subtle) {
150
+ throw new Error(
151
+ 'No WebCrypto available in this runtime. On React Native (or any host without ' +
152
+ 'SubtleCrypto), call configureCrypto({ getRandomValues, sha256 }) before using PKCE.'
153
+ );
154
+ }
155
+ return {
156
+ getRandomValues: (bytes) => wc.getRandomValues(bytes),
157
+ sha256: async (bytes) => new Uint8Array(await wc.subtle.digest('SHA-256', bytes)),
158
+ };
159
+ }
160
+
161
+ crypto = { configureCrypto, resetCrypto, getCrypto };
162
+ return crypto;
163
+ }
164
+
165
+ var pkce;
166
+ var hasRequiredPkce;
167
+
168
+ function requirePkce () {
169
+ if (hasRequiredPkce) return pkce;
170
+ hasRequiredPkce = 1;
171
+
172
+ const { base64urlEncode } = requireBase64();
173
+ const { getCrypto } = requireCrypto();
174
+
30
175
  /**
31
176
  * Generates a 64-character PKCE code verifier from [A-Za-z0-9-._~].
32
177
  * @returns {string}
33
178
  */
34
179
  function generateCodeVerifier() {
35
180
  const bytes = new Uint8Array(48);
36
- globalThis.crypto.getRandomValues(bytes);
181
+ getCrypto().getRandomValues(bytes);
37
182
  return base64urlEncode(bytes).slice(0, 64);
38
183
  }
39
184
 
@@ -43,9 +188,8 @@ function requirePkce () {
43
188
  * @returns {Promise<string>}
44
189
  */
45
190
  async function generateCodeChallenge(verifier) {
46
- const encoded = new TextEncoder().encode(verifier);
47
- const hashBuf = await globalThis.crypto.subtle.digest('SHA-256', encoded);
48
- return base64urlEncode(new Uint8Array(hashBuf));
191
+ const hash = await getCrypto().sha256(asciiEncode(verifier));
192
+ return base64urlEncode(hash);
49
193
  }
50
194
 
51
195
  /**
@@ -54,8 +198,24 @@ function requirePkce () {
54
198
  */
55
199
  function generateState() {
56
200
  const bytes = new Uint8Array(16);
57
- globalThis.crypto.getRandomValues(bytes);
58
- return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
201
+ getCrypto().getRandomValues(bytes);
202
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
203
+ }
204
+
205
+ // helpers
206
+
207
+ // The RFC 7636 §4.1 verifier alphabet is ASCII-only, so a byte-per-char encode
208
+ // is exact and keeps TextEncoder — absent on some React Native versions — off
209
+ // the PKCE path entirely. A non-ASCII verifier is invalid input, not something
210
+ // to silently re-encode.
211
+ function asciiEncode(str) {
212
+ const out = new Uint8Array(str.length);
213
+ for (let i = 0; i < str.length; i++) {
214
+ const code = str.charCodeAt(i);
215
+ if (code > 0x7f) throw new TypeError('PKCE code_verifier must be ASCII (RFC 7636 §4.1)');
216
+ out[i] = code;
217
+ }
218
+ return out;
59
219
  }
60
220
 
61
221
  pkce = { generateCodeVerifier, generateCodeChallenge, generateState };
@@ -178,6 +338,7 @@ function requireClient () {
178
338
 
179
339
  const { generateCodeVerifier, generateCodeChallenge, generateState } = requirePkce();
180
340
  const { postForm, postJson } = requireHttp();
341
+ const { base64urlDecode, utf8Decode } = requireBase64();
181
342
  const { OAuthError } = requireErrors();
182
343
 
183
344
  // In-flight dedup key for client_credentials re-mints (no refresh_token to key on).
@@ -253,8 +414,7 @@ function requireClient () {
253
414
  code,
254
415
  code_verifier: codeVerifier,
255
416
  redirect_uri: redirectUri ?? this._config.redirectUri,
256
- client_id: this._config.clientId,
257
- client_secret: this._config.clientSecret,
417
+ ...this._clientAuth(),
258
418
  });
259
419
  }
260
420
 
@@ -266,8 +426,7 @@ function requireClient () {
266
426
  async getClientCredentialsToken() {
267
427
  return postForm(`${this._config.baseUrl}/oauth/token`, {
268
428
  grant_type: 'client_credentials',
269
- client_id: this._config.clientId,
270
- client_secret: this._config.clientSecret,
429
+ ...this._clientAuth(),
271
430
  });
272
431
  }
273
432
 
@@ -279,8 +438,7 @@ function requireClient () {
279
438
  return postForm(`${this._config.baseUrl}/oauth/token`, {
280
439
  grant_type: 'refresh_token',
281
440
  refresh_token: refreshToken,
282
- client_id: this._config.clientId,
283
- client_secret: this._config.clientSecret,
441
+ ...this._clientAuth(),
284
442
  });
285
443
  }
286
444
 
@@ -290,7 +448,7 @@ function requireClient () {
290
448
  * @returns {Promise<void>}
291
449
  */
292
450
  async revokeToken(token, tokenTypeHint) {
293
- const params = { token, client_id: this._config.clientId, client_secret: this._config.clientSecret };
451
+ const params = { token, ...this._clientAuth() };
294
452
  if (tokenTypeHint) params.token_type_hint = tokenTypeHint;
295
453
  await postForm(`${this._config.baseUrl}/oauth/revoke`, params);
296
454
  }
@@ -371,10 +529,22 @@ function requireClient () {
371
529
  decodeAccessToken(accessToken) {
372
530
  const segment = accessToken.split('.')[1];
373
531
  if (!segment) throw new OAuthError('invalid_token', 'Malformed JWT', 400);
374
- const b64 = segment.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(segment.length / 4) * 4, '=');
375
- const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
376
- const payload = JSON.parse(new TextDecoder().decode(bytes));
377
- return payload;
532
+ return JSON.parse(utf8Decode(base64urlDecode(segment)));
533
+ }
534
+
535
+ /**
536
+ * Client-authentication params for a token-endpoint request.
537
+ *
538
+ * A public client (mobile, SPA) holds no secret, and the authorization server
539
+ * rejects the request outright if one is present, so the key is omitted
540
+ * entirely rather than sent empty. Passing it through unconditionally
541
+ * serializes `undefined` into the form body as that literal string.
542
+ * @returns {Record<string, string>}
543
+ */
544
+ _clientAuth() {
545
+ const params = { client_id: this._config.clientId };
546
+ if (this._config.clientSecret) params.client_secret = this._config.clientSecret;
547
+ return params;
378
548
  }
379
549
  }
380
550
 
@@ -471,8 +641,9 @@ function requireSrc$1 () {
471
641
  const { OAuthClient } = requireClient();
472
642
  const { OAuthError } = requireErrors();
473
643
  const { OAuthErrorCode } = requireTypes();
644
+ const { configureCrypto } = requireCrypto();
474
645
 
475
- src$1 = { OAuthClient, OAuthError, OAuthErrorCode };
646
+ src$1 = { OAuthClient, OAuthError, OAuthErrorCode, configureCrypto };
476
647
  return src$1;
477
648
  }
478
649
 
@@ -480,7 +651,7 @@ var srcExports$1 = requireSrc$1();
480
651
  var _oauth = /*@__PURE__*/getDefaultExportFromCjs(srcExports$1);
481
652
 
482
653
  // Auto-generated — do not edit
483
- const { OAuthClient, OAuthError, OAuthErrorCode } = _oauth;
654
+ const { OAuthClient, OAuthError, OAuthErrorCode, configureCrypto } = _oauth;
484
655
 
485
656
  var signing;
486
657
  var hasRequiredSigning;
@@ -497,12 +668,21 @@ function requireSigning () {
497
668
  // constant time. Web Crypto only, so the same code runs in Node >= 18 and
498
669
  // browsers.
499
670
 
500
- const _encoder = new TextEncoder();
671
+ // Built on first use, not at import. These functions are a server-side concern
672
+ // (only the endpoint receiving a push verifies a signature), but the module is
673
+ // bundled into every SDK including React Native — and constructing this at
674
+ // module scope made merely importing the SDK crash on any runtime without a
675
+ // TextEncoder global.
676
+ let _encoderInstance = null;
677
+ function _encode(text) {
678
+ if (!_encoderInstance) _encoderInstance = new TextEncoder();
679
+ return _encoderInstance.encode(text);
680
+ }
501
681
 
502
682
  async function signWebhookPayload(secret, timestamp, rawBody) {
503
683
  const key = await globalThis.crypto.subtle.importKey(
504
- 'raw', _encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
505
- const payload = _concat(_encoder.encode(`${timestamp}.`), toBytes(rawBody));
684
+ 'raw', _encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
685
+ const payload = _concat(_encode(`${timestamp}.`), toBytes(rawBody));
506
686
  const digest = await globalThis.crypto.subtle.sign('HMAC', key, payload);
507
687
  return 'sha256=' + _hex(new Uint8Array(digest));
508
688
  }
@@ -526,7 +706,7 @@ function requireSigning () {
526
706
 
527
707
  // helpers
528
708
  function toBytes(rawBody) {
529
- if (typeof rawBody === 'string') return _encoder.encode(rawBody);
709
+ if (typeof rawBody === 'string') return _encode(rawBody);
530
710
  if (rawBody instanceof Uint8Array) return rawBody;
531
711
  if (rawBody instanceof ArrayBuffer) return new Uint8Array(rawBody);
532
712
  throw new TypeError('rawBody must be a string, Uint8Array, or ArrayBuffer');
@@ -2396,6 +2576,7 @@ exports.ValidationError = ValidationError;
2396
2576
  exports.WEBHOOK_MODEL_COLUMNS = WEBHOOK_MODEL_COLUMNS;
2397
2577
  exports.WEBHOOK_OUTCOME_MODELS = WEBHOOK_OUTCOME_MODELS;
2398
2578
  exports.WebhookConsumer = WebhookConsumer;
2579
+ exports.configureCrypto = configureCrypto;
2399
2580
  exports.handleResponse = handleResponse;
2400
2581
  exports.selectResultColumns = selectResultColumns;
2401
2582
  exports.signWebhookPayload = signWebhookPayload;
package/dist/sdk.esm.js CHANGED
@@ -2,36 +2,181 @@ function getDefaultExportFromCjs (x) {
2
2
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
3
3
  }
4
4
 
5
- var pkce;
6
- var hasRequiredPkce;
5
+ var base64;
6
+ var hasRequiredBase64;
7
7
 
8
- function requirePkce () {
9
- if (hasRequiredPkce) return pkce;
10
- hasRequiredPkce = 1;
8
+ function requireBase64 () {
9
+ if (hasRequiredBase64) return base64;
10
+ hasRequiredBase64 = 1;
11
11
 
12
- // base64url-encode a Uint8Array without padding
12
+ // Base64URL codec, used by PKCE (encode) and JWT payload decoding (decode).
13
+ // Hand-rolled rather than delegating to atob/btoa, Buffer, or TextDecoder:
14
+ // none of those is present on every target runtime — React Native's Hermes has
15
+ // no Buffer and does not guarantee atob — and the alphabet here is URL-safe and
16
+ // unpadded, which the standard helpers do not produce.
17
+
18
+ const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
19
+
20
+ /**
21
+ * @param {Uint8Array} bytes
22
+ * @returns {string} unpadded base64url
23
+ */
13
24
  function base64urlEncode(bytes) {
14
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
15
25
  let result = '';
16
26
  for (let i = 0; i < bytes.length; i += 3) {
17
27
  const b0 = bytes[i];
18
28
  const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
19
29
  const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
20
- result += chars[b0 >> 2];
21
- result += chars[((b0 & 3) << 4) | (b1 >> 4)];
22
- if (i + 1 < bytes.length) result += chars[((b1 & 0xf) << 2) | (b2 >> 6)];
23
- if (i + 2 < bytes.length) result += chars[b2 & 0x3f];
30
+ result += ALPHABET[b0 >> 2];
31
+ result += ALPHABET[((b0 & 3) << 4) | (b1 >> 4)];
32
+ if (i + 1 < bytes.length) result += ALPHABET[((b1 & 0xf) << 2) | (b2 >> 6)];
33
+ if (i + 2 < bytes.length) result += ALPHABET[b2 & 0x3f];
24
34
  }
25
35
  return result;
26
36
  }
27
37
 
38
+ /**
39
+ * Decodes base64url or standard base64, padded or not.
40
+ * @param {string} str
41
+ * @returns {Uint8Array}
42
+ */
43
+ function base64urlDecode(str) {
44
+ const clean = String(str).replace(/=+$/, '');
45
+ const out = new Uint8Array((clean.length * 3) >> 2);
46
+ let acc = 0;
47
+ let bits = 0;
48
+ let i = 0;
49
+ for (const ch of clean) {
50
+ const value = decodeChar(ch);
51
+ if (value < 0) throw new TypeError(`Invalid base64url character: ${JSON.stringify(ch)}`);
52
+ acc = (acc << 6) | value;
53
+ bits += 6;
54
+ if (bits >= 8) {
55
+ bits -= 8;
56
+ out[i++] = (acc >> bits) & 0xff;
57
+ }
58
+ }
59
+ return out.subarray(0, i);
60
+ }
61
+
62
+ /**
63
+ * @param {Uint8Array} bytes
64
+ * @returns {string}
65
+ */
66
+ function utf8Decode(bytes) {
67
+ let out = '';
68
+ let i = 0;
69
+ while (i < bytes.length) {
70
+ const b0 = bytes[i++];
71
+ let codePoint;
72
+ if (b0 < 0x80) codePoint = b0;
73
+ else if ((b0 & 0xe0) === 0xc0) codePoint = ((b0 & 0x1f) << 6) | (bytes[i++] & 0x3f);
74
+ else if ((b0 & 0xf0) === 0xe0)
75
+ codePoint = ((b0 & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
76
+ else
77
+ codePoint =
78
+ ((b0 & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
79
+ out += String.fromCodePoint(codePoint);
80
+ }
81
+ return out;
82
+ }
83
+
84
+ // helpers
85
+ function decodeChar(ch) {
86
+ // '+' and '/' map onto the URL-safe alphabet's '-' and '_' slots, so standard
87
+ // base64 decodes through the same table.
88
+ if (ch === '+') return 62;
89
+ if (ch === '/') return 63;
90
+ return ALPHABET.indexOf(ch);
91
+ }
92
+
93
+ base64 = { base64urlEncode, base64urlDecode, utf8Decode };
94
+ return base64;
95
+ }
96
+
97
+ var crypto;
98
+ var hasRequiredCrypto;
99
+
100
+ function requireCrypto () {
101
+ if (hasRequiredCrypto) return crypto;
102
+ hasRequiredCrypto = 1;
103
+
104
+ // Crypto seam for PKCE.
105
+ //
106
+ // Defaults to WebCrypto, which covers browsers and Node >= 18. React Native's
107
+ // Hermes runtime provides neither half of what PKCE needs: there is no
108
+ // SubtleCrypto at all (so no SHA-256), and no crypto.getRandomValues without a
109
+ // polyfill. Hosts on such runtimes call configureCrypto() once at startup with
110
+ // native-backed equivalents (expo-crypto, react-native-quick-crypto).
111
+
112
+ /** @typedef {{getRandomValues: (bytes: Uint8Array) => Uint8Array, sha256: (bytes: Uint8Array) => Promise<Uint8Array>}} CryptoProvider */
113
+
114
+ let _provider = null;
115
+
116
+ /**
117
+ * Installs a crypto provider, replacing the WebCrypto default.
118
+ * @param {CryptoProvider} provider
119
+ */
120
+ function configureCrypto(provider) {
121
+ if (!provider || typeof provider.getRandomValues !== 'function' || typeof provider.sha256 !== 'function') {
122
+ throw new TypeError(
123
+ 'configureCrypto requires { getRandomValues(bytes) => bytes, sha256(bytes) => Promise<Uint8Array> }'
124
+ );
125
+ }
126
+ _provider = provider;
127
+ }
128
+
129
+ /** Restores the WebCrypto default. Primarily for tests. */
130
+ function resetCrypto() {
131
+ _provider = null;
132
+ }
133
+
134
+ /**
135
+ * @returns {CryptoProvider}
136
+ */
137
+ function getCrypto() {
138
+ return _provider ?? webCryptoProvider();
139
+ }
140
+
141
+ // helpers
142
+ function webCryptoProvider() {
143
+ const wc = globalThis.crypto;
144
+ // Named explicitly rather than letting a property access blow up deep inside
145
+ // PKCE: on React Native the failure is a missing polyfill, and the message
146
+ // has to say so.
147
+ if (!wc || typeof wc.getRandomValues !== 'function' || !wc.subtle) {
148
+ throw new Error(
149
+ 'No WebCrypto available in this runtime. On React Native (or any host without ' +
150
+ 'SubtleCrypto), call configureCrypto({ getRandomValues, sha256 }) before using PKCE.'
151
+ );
152
+ }
153
+ return {
154
+ getRandomValues: (bytes) => wc.getRandomValues(bytes),
155
+ sha256: async (bytes) => new Uint8Array(await wc.subtle.digest('SHA-256', bytes)),
156
+ };
157
+ }
158
+
159
+ crypto = { configureCrypto, resetCrypto, getCrypto };
160
+ return crypto;
161
+ }
162
+
163
+ var pkce;
164
+ var hasRequiredPkce;
165
+
166
+ function requirePkce () {
167
+ if (hasRequiredPkce) return pkce;
168
+ hasRequiredPkce = 1;
169
+
170
+ const { base64urlEncode } = requireBase64();
171
+ const { getCrypto } = requireCrypto();
172
+
28
173
  /**
29
174
  * Generates a 64-character PKCE code verifier from [A-Za-z0-9-._~].
30
175
  * @returns {string}
31
176
  */
32
177
  function generateCodeVerifier() {
33
178
  const bytes = new Uint8Array(48);
34
- globalThis.crypto.getRandomValues(bytes);
179
+ getCrypto().getRandomValues(bytes);
35
180
  return base64urlEncode(bytes).slice(0, 64);
36
181
  }
37
182
 
@@ -41,9 +186,8 @@ function requirePkce () {
41
186
  * @returns {Promise<string>}
42
187
  */
43
188
  async function generateCodeChallenge(verifier) {
44
- const encoded = new TextEncoder().encode(verifier);
45
- const hashBuf = await globalThis.crypto.subtle.digest('SHA-256', encoded);
46
- return base64urlEncode(new Uint8Array(hashBuf));
189
+ const hash = await getCrypto().sha256(asciiEncode(verifier));
190
+ return base64urlEncode(hash);
47
191
  }
48
192
 
49
193
  /**
@@ -52,8 +196,24 @@ function requirePkce () {
52
196
  */
53
197
  function generateState() {
54
198
  const bytes = new Uint8Array(16);
55
- globalThis.crypto.getRandomValues(bytes);
56
- return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
199
+ getCrypto().getRandomValues(bytes);
200
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
201
+ }
202
+
203
+ // helpers
204
+
205
+ // The RFC 7636 §4.1 verifier alphabet is ASCII-only, so a byte-per-char encode
206
+ // is exact and keeps TextEncoder — absent on some React Native versions — off
207
+ // the PKCE path entirely. A non-ASCII verifier is invalid input, not something
208
+ // to silently re-encode.
209
+ function asciiEncode(str) {
210
+ const out = new Uint8Array(str.length);
211
+ for (let i = 0; i < str.length; i++) {
212
+ const code = str.charCodeAt(i);
213
+ if (code > 0x7f) throw new TypeError('PKCE code_verifier must be ASCII (RFC 7636 §4.1)');
214
+ out[i] = code;
215
+ }
216
+ return out;
57
217
  }
58
218
 
59
219
  pkce = { generateCodeVerifier, generateCodeChallenge, generateState };
@@ -176,6 +336,7 @@ function requireClient () {
176
336
 
177
337
  const { generateCodeVerifier, generateCodeChallenge, generateState } = requirePkce();
178
338
  const { postForm, postJson } = requireHttp();
339
+ const { base64urlDecode, utf8Decode } = requireBase64();
179
340
  const { OAuthError } = requireErrors();
180
341
 
181
342
  // In-flight dedup key for client_credentials re-mints (no refresh_token to key on).
@@ -251,8 +412,7 @@ function requireClient () {
251
412
  code,
252
413
  code_verifier: codeVerifier,
253
414
  redirect_uri: redirectUri ?? this._config.redirectUri,
254
- client_id: this._config.clientId,
255
- client_secret: this._config.clientSecret,
415
+ ...this._clientAuth(),
256
416
  });
257
417
  }
258
418
 
@@ -264,8 +424,7 @@ function requireClient () {
264
424
  async getClientCredentialsToken() {
265
425
  return postForm(`${this._config.baseUrl}/oauth/token`, {
266
426
  grant_type: 'client_credentials',
267
- client_id: this._config.clientId,
268
- client_secret: this._config.clientSecret,
427
+ ...this._clientAuth(),
269
428
  });
270
429
  }
271
430
 
@@ -277,8 +436,7 @@ function requireClient () {
277
436
  return postForm(`${this._config.baseUrl}/oauth/token`, {
278
437
  grant_type: 'refresh_token',
279
438
  refresh_token: refreshToken,
280
- client_id: this._config.clientId,
281
- client_secret: this._config.clientSecret,
439
+ ...this._clientAuth(),
282
440
  });
283
441
  }
284
442
 
@@ -288,7 +446,7 @@ function requireClient () {
288
446
  * @returns {Promise<void>}
289
447
  */
290
448
  async revokeToken(token, tokenTypeHint) {
291
- const params = { token, client_id: this._config.clientId, client_secret: this._config.clientSecret };
449
+ const params = { token, ...this._clientAuth() };
292
450
  if (tokenTypeHint) params.token_type_hint = tokenTypeHint;
293
451
  await postForm(`${this._config.baseUrl}/oauth/revoke`, params);
294
452
  }
@@ -369,10 +527,22 @@ function requireClient () {
369
527
  decodeAccessToken(accessToken) {
370
528
  const segment = accessToken.split('.')[1];
371
529
  if (!segment) throw new OAuthError('invalid_token', 'Malformed JWT', 400);
372
- const b64 = segment.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(segment.length / 4) * 4, '=');
373
- const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
374
- const payload = JSON.parse(new TextDecoder().decode(bytes));
375
- return payload;
530
+ return JSON.parse(utf8Decode(base64urlDecode(segment)));
531
+ }
532
+
533
+ /**
534
+ * Client-authentication params for a token-endpoint request.
535
+ *
536
+ * A public client (mobile, SPA) holds no secret, and the authorization server
537
+ * rejects the request outright if one is present, so the key is omitted
538
+ * entirely rather than sent empty. Passing it through unconditionally
539
+ * serializes `undefined` into the form body as that literal string.
540
+ * @returns {Record<string, string>}
541
+ */
542
+ _clientAuth() {
543
+ const params = { client_id: this._config.clientId };
544
+ if (this._config.clientSecret) params.client_secret = this._config.clientSecret;
545
+ return params;
376
546
  }
377
547
  }
378
548
 
@@ -469,8 +639,9 @@ function requireSrc$1 () {
469
639
  const { OAuthClient } = requireClient();
470
640
  const { OAuthError } = requireErrors();
471
641
  const { OAuthErrorCode } = requireTypes();
642
+ const { configureCrypto } = requireCrypto();
472
643
 
473
- src$1 = { OAuthClient, OAuthError, OAuthErrorCode };
644
+ src$1 = { OAuthClient, OAuthError, OAuthErrorCode, configureCrypto };
474
645
  return src$1;
475
646
  }
476
647
 
@@ -478,7 +649,7 @@ var srcExports$1 = requireSrc$1();
478
649
  var _oauth = /*@__PURE__*/getDefaultExportFromCjs(srcExports$1);
479
650
 
480
651
  // Auto-generated — do not edit
481
- const { OAuthClient, OAuthError, OAuthErrorCode } = _oauth;
652
+ const { OAuthClient, OAuthError, OAuthErrorCode, configureCrypto } = _oauth;
482
653
 
483
654
  var signing;
484
655
  var hasRequiredSigning;
@@ -495,12 +666,21 @@ function requireSigning () {
495
666
  // constant time. Web Crypto only, so the same code runs in Node >= 18 and
496
667
  // browsers.
497
668
 
498
- const _encoder = new TextEncoder();
669
+ // Built on first use, not at import. These functions are a server-side concern
670
+ // (only the endpoint receiving a push verifies a signature), but the module is
671
+ // bundled into every SDK including React Native — and constructing this at
672
+ // module scope made merely importing the SDK crash on any runtime without a
673
+ // TextEncoder global.
674
+ let _encoderInstance = null;
675
+ function _encode(text) {
676
+ if (!_encoderInstance) _encoderInstance = new TextEncoder();
677
+ return _encoderInstance.encode(text);
678
+ }
499
679
 
500
680
  async function signWebhookPayload(secret, timestamp, rawBody) {
501
681
  const key = await globalThis.crypto.subtle.importKey(
502
- 'raw', _encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
503
- const payload = _concat(_encoder.encode(`${timestamp}.`), toBytes(rawBody));
682
+ 'raw', _encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
683
+ const payload = _concat(_encode(`${timestamp}.`), toBytes(rawBody));
504
684
  const digest = await globalThis.crypto.subtle.sign('HMAC', key, payload);
505
685
  return 'sha256=' + _hex(new Uint8Array(digest));
506
686
  }
@@ -524,7 +704,7 @@ function requireSigning () {
524
704
 
525
705
  // helpers
526
706
  function toBytes(rawBody) {
527
- if (typeof rawBody === 'string') return _encoder.encode(rawBody);
707
+ if (typeof rawBody === 'string') return _encode(rawBody);
528
708
  if (rawBody instanceof Uint8Array) return rawBody;
529
709
  if (rawBody instanceof ArrayBuffer) return new Uint8Array(rawBody);
530
710
  throw new TypeError('rawBody must be a string, Uint8Array, or ArrayBuffer');
@@ -2363,4 +2543,4 @@ class MediaViz {
2363
2543
  get refreshToken() { return this._refreshToken; }
2364
2544
  }
2365
2545
 
2366
- export { Admin, AiModelCredits, ApiError, Company, CuratedAlbums, CustomAlbums, EmailTokens, Health, InMemoryWebhookStore, Keywords, MediaViz, NotFoundError, OAuthClient, OAuthError, OAuthErrorCode, OauthAuthorization, OauthLogin, OauthToken, Person, Photos, Photoupload, Projects, RateLimitError, Search, ServerError, Subscription, Users, ValidationError, WEBHOOK_MODEL_COLUMNS, WEBHOOK_OUTCOME_MODELS, WebhookConsumer, handleResponse, selectResultColumns, signWebhookPayload, verifyWebhookSignature };
2546
+ export { Admin, AiModelCredits, ApiError, Company, CuratedAlbums, CustomAlbums, EmailTokens, Health, InMemoryWebhookStore, Keywords, MediaViz, NotFoundError, OAuthClient, OAuthError, OAuthErrorCode, OauthAuthorization, OauthLogin, OauthToken, Person, Photos, Photoupload, Projects, RateLimitError, Search, ServerError, Subscription, Users, ValidationError, WEBHOOK_MODEL_COLUMNS, WEBHOOK_OUTCOME_MODELS, WebhookConsumer, configureCrypto, handleResponse, selectResultColumns, signWebhookPayload, verifyWebhookSignature };
package/dist/sdk.umd.js CHANGED
@@ -8,36 +8,181 @@
8
8
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
9
9
  }
10
10
 
11
- var pkce;
12
- var hasRequiredPkce;
11
+ var base64;
12
+ var hasRequiredBase64;
13
13
 
14
- function requirePkce () {
15
- if (hasRequiredPkce) return pkce;
16
- hasRequiredPkce = 1;
14
+ function requireBase64 () {
15
+ if (hasRequiredBase64) return base64;
16
+ hasRequiredBase64 = 1;
17
17
 
18
- // base64url-encode a Uint8Array without padding
18
+ // Base64URL codec, used by PKCE (encode) and JWT payload decoding (decode).
19
+ // Hand-rolled rather than delegating to atob/btoa, Buffer, or TextDecoder:
20
+ // none of those is present on every target runtime — React Native's Hermes has
21
+ // no Buffer and does not guarantee atob — and the alphabet here is URL-safe and
22
+ // unpadded, which the standard helpers do not produce.
23
+
24
+ const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
25
+
26
+ /**
27
+ * @param {Uint8Array} bytes
28
+ * @returns {string} unpadded base64url
29
+ */
19
30
  function base64urlEncode(bytes) {
20
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
21
31
  let result = '';
22
32
  for (let i = 0; i < bytes.length; i += 3) {
23
33
  const b0 = bytes[i];
24
34
  const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
25
35
  const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
26
- result += chars[b0 >> 2];
27
- result += chars[((b0 & 3) << 4) | (b1 >> 4)];
28
- if (i + 1 < bytes.length) result += chars[((b1 & 0xf) << 2) | (b2 >> 6)];
29
- if (i + 2 < bytes.length) result += chars[b2 & 0x3f];
36
+ result += ALPHABET[b0 >> 2];
37
+ result += ALPHABET[((b0 & 3) << 4) | (b1 >> 4)];
38
+ if (i + 1 < bytes.length) result += ALPHABET[((b1 & 0xf) << 2) | (b2 >> 6)];
39
+ if (i + 2 < bytes.length) result += ALPHABET[b2 & 0x3f];
30
40
  }
31
41
  return result;
32
42
  }
33
43
 
44
+ /**
45
+ * Decodes base64url or standard base64, padded or not.
46
+ * @param {string} str
47
+ * @returns {Uint8Array}
48
+ */
49
+ function base64urlDecode(str) {
50
+ const clean = String(str).replace(/=+$/, '');
51
+ const out = new Uint8Array((clean.length * 3) >> 2);
52
+ let acc = 0;
53
+ let bits = 0;
54
+ let i = 0;
55
+ for (const ch of clean) {
56
+ const value = decodeChar(ch);
57
+ if (value < 0) throw new TypeError(`Invalid base64url character: ${JSON.stringify(ch)}`);
58
+ acc = (acc << 6) | value;
59
+ bits += 6;
60
+ if (bits >= 8) {
61
+ bits -= 8;
62
+ out[i++] = (acc >> bits) & 0xff;
63
+ }
64
+ }
65
+ return out.subarray(0, i);
66
+ }
67
+
68
+ /**
69
+ * @param {Uint8Array} bytes
70
+ * @returns {string}
71
+ */
72
+ function utf8Decode(bytes) {
73
+ let out = '';
74
+ let i = 0;
75
+ while (i < bytes.length) {
76
+ const b0 = bytes[i++];
77
+ let codePoint;
78
+ if (b0 < 0x80) codePoint = b0;
79
+ else if ((b0 & 0xe0) === 0xc0) codePoint = ((b0 & 0x1f) << 6) | (bytes[i++] & 0x3f);
80
+ else if ((b0 & 0xf0) === 0xe0)
81
+ codePoint = ((b0 & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
82
+ else
83
+ codePoint =
84
+ ((b0 & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
85
+ out += String.fromCodePoint(codePoint);
86
+ }
87
+ return out;
88
+ }
89
+
90
+ // helpers
91
+ function decodeChar(ch) {
92
+ // '+' and '/' map onto the URL-safe alphabet's '-' and '_' slots, so standard
93
+ // base64 decodes through the same table.
94
+ if (ch === '+') return 62;
95
+ if (ch === '/') return 63;
96
+ return ALPHABET.indexOf(ch);
97
+ }
98
+
99
+ base64 = { base64urlEncode, base64urlDecode, utf8Decode };
100
+ return base64;
101
+ }
102
+
103
+ var crypto;
104
+ var hasRequiredCrypto;
105
+
106
+ function requireCrypto () {
107
+ if (hasRequiredCrypto) return crypto;
108
+ hasRequiredCrypto = 1;
109
+
110
+ // Crypto seam for PKCE.
111
+ //
112
+ // Defaults to WebCrypto, which covers browsers and Node >= 18. React Native's
113
+ // Hermes runtime provides neither half of what PKCE needs: there is no
114
+ // SubtleCrypto at all (so no SHA-256), and no crypto.getRandomValues without a
115
+ // polyfill. Hosts on such runtimes call configureCrypto() once at startup with
116
+ // native-backed equivalents (expo-crypto, react-native-quick-crypto).
117
+
118
+ /** @typedef {{getRandomValues: (bytes: Uint8Array) => Uint8Array, sha256: (bytes: Uint8Array) => Promise<Uint8Array>}} CryptoProvider */
119
+
120
+ let _provider = null;
121
+
122
+ /**
123
+ * Installs a crypto provider, replacing the WebCrypto default.
124
+ * @param {CryptoProvider} provider
125
+ */
126
+ function configureCrypto(provider) {
127
+ if (!provider || typeof provider.getRandomValues !== 'function' || typeof provider.sha256 !== 'function') {
128
+ throw new TypeError(
129
+ 'configureCrypto requires { getRandomValues(bytes) => bytes, sha256(bytes) => Promise<Uint8Array> }'
130
+ );
131
+ }
132
+ _provider = provider;
133
+ }
134
+
135
+ /** Restores the WebCrypto default. Primarily for tests. */
136
+ function resetCrypto() {
137
+ _provider = null;
138
+ }
139
+
140
+ /**
141
+ * @returns {CryptoProvider}
142
+ */
143
+ function getCrypto() {
144
+ return _provider ?? webCryptoProvider();
145
+ }
146
+
147
+ // helpers
148
+ function webCryptoProvider() {
149
+ const wc = globalThis.crypto;
150
+ // Named explicitly rather than letting a property access blow up deep inside
151
+ // PKCE: on React Native the failure is a missing polyfill, and the message
152
+ // has to say so.
153
+ if (!wc || typeof wc.getRandomValues !== 'function' || !wc.subtle) {
154
+ throw new Error(
155
+ 'No WebCrypto available in this runtime. On React Native (or any host without ' +
156
+ 'SubtleCrypto), call configureCrypto({ getRandomValues, sha256 }) before using PKCE.'
157
+ );
158
+ }
159
+ return {
160
+ getRandomValues: (bytes) => wc.getRandomValues(bytes),
161
+ sha256: async (bytes) => new Uint8Array(await wc.subtle.digest('SHA-256', bytes)),
162
+ };
163
+ }
164
+
165
+ crypto = { configureCrypto, resetCrypto, getCrypto };
166
+ return crypto;
167
+ }
168
+
169
+ var pkce;
170
+ var hasRequiredPkce;
171
+
172
+ function requirePkce () {
173
+ if (hasRequiredPkce) return pkce;
174
+ hasRequiredPkce = 1;
175
+
176
+ const { base64urlEncode } = requireBase64();
177
+ const { getCrypto } = requireCrypto();
178
+
34
179
  /**
35
180
  * Generates a 64-character PKCE code verifier from [A-Za-z0-9-._~].
36
181
  * @returns {string}
37
182
  */
38
183
  function generateCodeVerifier() {
39
184
  const bytes = new Uint8Array(48);
40
- globalThis.crypto.getRandomValues(bytes);
185
+ getCrypto().getRandomValues(bytes);
41
186
  return base64urlEncode(bytes).slice(0, 64);
42
187
  }
43
188
 
@@ -47,9 +192,8 @@
47
192
  * @returns {Promise<string>}
48
193
  */
49
194
  async function generateCodeChallenge(verifier) {
50
- const encoded = new TextEncoder().encode(verifier);
51
- const hashBuf = await globalThis.crypto.subtle.digest('SHA-256', encoded);
52
- return base64urlEncode(new Uint8Array(hashBuf));
195
+ const hash = await getCrypto().sha256(asciiEncode(verifier));
196
+ return base64urlEncode(hash);
53
197
  }
54
198
 
55
199
  /**
@@ -58,8 +202,24 @@
58
202
  */
59
203
  function generateState() {
60
204
  const bytes = new Uint8Array(16);
61
- globalThis.crypto.getRandomValues(bytes);
62
- return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
205
+ getCrypto().getRandomValues(bytes);
206
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
207
+ }
208
+
209
+ // helpers
210
+
211
+ // The RFC 7636 §4.1 verifier alphabet is ASCII-only, so a byte-per-char encode
212
+ // is exact and keeps TextEncoder — absent on some React Native versions — off
213
+ // the PKCE path entirely. A non-ASCII verifier is invalid input, not something
214
+ // to silently re-encode.
215
+ function asciiEncode(str) {
216
+ const out = new Uint8Array(str.length);
217
+ for (let i = 0; i < str.length; i++) {
218
+ const code = str.charCodeAt(i);
219
+ if (code > 0x7f) throw new TypeError('PKCE code_verifier must be ASCII (RFC 7636 §4.1)');
220
+ out[i] = code;
221
+ }
222
+ return out;
63
223
  }
64
224
 
65
225
  pkce = { generateCodeVerifier, generateCodeChallenge, generateState };
@@ -182,6 +342,7 @@
182
342
 
183
343
  const { generateCodeVerifier, generateCodeChallenge, generateState } = requirePkce();
184
344
  const { postForm, postJson } = requireHttp();
345
+ const { base64urlDecode, utf8Decode } = requireBase64();
185
346
  const { OAuthError } = requireErrors();
186
347
 
187
348
  // In-flight dedup key for client_credentials re-mints (no refresh_token to key on).
@@ -257,8 +418,7 @@
257
418
  code,
258
419
  code_verifier: codeVerifier,
259
420
  redirect_uri: redirectUri ?? this._config.redirectUri,
260
- client_id: this._config.clientId,
261
- client_secret: this._config.clientSecret,
421
+ ...this._clientAuth(),
262
422
  });
263
423
  }
264
424
 
@@ -270,8 +430,7 @@
270
430
  async getClientCredentialsToken() {
271
431
  return postForm(`${this._config.baseUrl}/oauth/token`, {
272
432
  grant_type: 'client_credentials',
273
- client_id: this._config.clientId,
274
- client_secret: this._config.clientSecret,
433
+ ...this._clientAuth(),
275
434
  });
276
435
  }
277
436
 
@@ -283,8 +442,7 @@
283
442
  return postForm(`${this._config.baseUrl}/oauth/token`, {
284
443
  grant_type: 'refresh_token',
285
444
  refresh_token: refreshToken,
286
- client_id: this._config.clientId,
287
- client_secret: this._config.clientSecret,
445
+ ...this._clientAuth(),
288
446
  });
289
447
  }
290
448
 
@@ -294,7 +452,7 @@
294
452
  * @returns {Promise<void>}
295
453
  */
296
454
  async revokeToken(token, tokenTypeHint) {
297
- const params = { token, client_id: this._config.clientId, client_secret: this._config.clientSecret };
455
+ const params = { token, ...this._clientAuth() };
298
456
  if (tokenTypeHint) params.token_type_hint = tokenTypeHint;
299
457
  await postForm(`${this._config.baseUrl}/oauth/revoke`, params);
300
458
  }
@@ -375,10 +533,22 @@
375
533
  decodeAccessToken(accessToken) {
376
534
  const segment = accessToken.split('.')[1];
377
535
  if (!segment) throw new OAuthError('invalid_token', 'Malformed JWT', 400);
378
- const b64 = segment.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(segment.length / 4) * 4, '=');
379
- const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
380
- const payload = JSON.parse(new TextDecoder().decode(bytes));
381
- return payload;
536
+ return JSON.parse(utf8Decode(base64urlDecode(segment)));
537
+ }
538
+
539
+ /**
540
+ * Client-authentication params for a token-endpoint request.
541
+ *
542
+ * A public client (mobile, SPA) holds no secret, and the authorization server
543
+ * rejects the request outright if one is present, so the key is omitted
544
+ * entirely rather than sent empty. Passing it through unconditionally
545
+ * serializes `undefined` into the form body as that literal string.
546
+ * @returns {Record<string, string>}
547
+ */
548
+ _clientAuth() {
549
+ const params = { client_id: this._config.clientId };
550
+ if (this._config.clientSecret) params.client_secret = this._config.clientSecret;
551
+ return params;
382
552
  }
383
553
  }
384
554
 
@@ -475,8 +645,9 @@
475
645
  const { OAuthClient } = requireClient();
476
646
  const { OAuthError } = requireErrors();
477
647
  const { OAuthErrorCode } = requireTypes();
648
+ const { configureCrypto } = requireCrypto();
478
649
 
479
- src$1 = { OAuthClient, OAuthError, OAuthErrorCode };
650
+ src$1 = { OAuthClient, OAuthError, OAuthErrorCode, configureCrypto };
480
651
  return src$1;
481
652
  }
482
653
 
@@ -484,7 +655,7 @@
484
655
  var _oauth = /*@__PURE__*/getDefaultExportFromCjs(srcExports$1);
485
656
 
486
657
  // Auto-generated — do not edit
487
- const { OAuthClient, OAuthError, OAuthErrorCode } = _oauth;
658
+ const { OAuthClient, OAuthError, OAuthErrorCode, configureCrypto } = _oauth;
488
659
 
489
660
  var signing;
490
661
  var hasRequiredSigning;
@@ -501,12 +672,21 @@
501
672
  // constant time. Web Crypto only, so the same code runs in Node >= 18 and
502
673
  // browsers.
503
674
 
504
- const _encoder = new TextEncoder();
675
+ // Built on first use, not at import. These functions are a server-side concern
676
+ // (only the endpoint receiving a push verifies a signature), but the module is
677
+ // bundled into every SDK including React Native — and constructing this at
678
+ // module scope made merely importing the SDK crash on any runtime without a
679
+ // TextEncoder global.
680
+ let _encoderInstance = null;
681
+ function _encode(text) {
682
+ if (!_encoderInstance) _encoderInstance = new TextEncoder();
683
+ return _encoderInstance.encode(text);
684
+ }
505
685
 
506
686
  async function signWebhookPayload(secret, timestamp, rawBody) {
507
687
  const key = await globalThis.crypto.subtle.importKey(
508
- 'raw', _encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
509
- const payload = _concat(_encoder.encode(`${timestamp}.`), toBytes(rawBody));
688
+ 'raw', _encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
689
+ const payload = _concat(_encode(`${timestamp}.`), toBytes(rawBody));
510
690
  const digest = await globalThis.crypto.subtle.sign('HMAC', key, payload);
511
691
  return 'sha256=' + _hex(new Uint8Array(digest));
512
692
  }
@@ -530,7 +710,7 @@
530
710
 
531
711
  // helpers
532
712
  function toBytes(rawBody) {
533
- if (typeof rawBody === 'string') return _encoder.encode(rawBody);
713
+ if (typeof rawBody === 'string') return _encode(rawBody);
534
714
  if (rawBody instanceof Uint8Array) return rawBody;
535
715
  if (rawBody instanceof ArrayBuffer) return new Uint8Array(rawBody);
536
716
  throw new TypeError('rawBody must be a string, Uint8Array, or ArrayBuffer');
@@ -2400,6 +2580,7 @@
2400
2580
  exports.WEBHOOK_MODEL_COLUMNS = WEBHOOK_MODEL_COLUMNS;
2401
2581
  exports.WEBHOOK_OUTCOME_MODELS = WEBHOOK_OUTCOME_MODELS;
2402
2582
  exports.WebhookConsumer = WebhookConsumer;
2583
+ exports.configureCrypto = configureCrypto;
2403
2584
  exports.handleResponse = handleResponse;
2404
2585
  exports.selectResultColumns = selectResultColumns;
2405
2586
  exports.signWebhookPayload = signWebhookPayload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediaviz/sdk",
3
- "version": "1.9.0-dev.106",
3
+ "version": "1.9.0-dev.117",
4
4
  "description": "MediaViz JavaScript SDK \u2014 auto-generated public endpoint client.",
5
5
  "license": "MIT",
6
6
  "repository": {