@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 +215 -34
- package/dist/sdk.esm.js +215 -35
- package/dist/sdk.umd.js +215 -34
- package/package.json +1 -1
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
|
|
8
|
-
var
|
|
7
|
+
var base64;
|
|
8
|
+
var hasRequiredBase64;
|
|
9
9
|
|
|
10
|
-
function
|
|
11
|
-
if (
|
|
12
|
-
|
|
10
|
+
function requireBase64 () {
|
|
11
|
+
if (hasRequiredBase64) return base64;
|
|
12
|
+
hasRequiredBase64 = 1;
|
|
13
13
|
|
|
14
|
-
//
|
|
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 +=
|
|
23
|
-
result +=
|
|
24
|
-
if (i + 1 < bytes.length) result +=
|
|
25
|
-
if (i + 2 < bytes.length) result +=
|
|
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
|
-
|
|
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
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
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',
|
|
505
|
-
const payload = _concat(
|
|
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
|
|
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
|
|
6
|
-
var
|
|
5
|
+
var base64;
|
|
6
|
+
var hasRequiredBase64;
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
if (
|
|
10
|
-
|
|
8
|
+
function requireBase64 () {
|
|
9
|
+
if (hasRequiredBase64) return base64;
|
|
10
|
+
hasRequiredBase64 = 1;
|
|
11
11
|
|
|
12
|
-
//
|
|
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 +=
|
|
21
|
-
result +=
|
|
22
|
-
if (i + 1 < bytes.length) result +=
|
|
23
|
-
if (i + 2 < bytes.length) result +=
|
|
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
|
-
|
|
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
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
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',
|
|
503
|
-
const payload = _concat(
|
|
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
|
|
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
|
|
12
|
-
var
|
|
11
|
+
var base64;
|
|
12
|
+
var hasRequiredBase64;
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
if (
|
|
16
|
-
|
|
14
|
+
function requireBase64 () {
|
|
15
|
+
if (hasRequiredBase64) return base64;
|
|
16
|
+
hasRequiredBase64 = 1;
|
|
17
17
|
|
|
18
|
-
//
|
|
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 +=
|
|
27
|
-
result +=
|
|
28
|
-
if (i + 1 < bytes.length) result +=
|
|
29
|
-
if (i + 2 < bytes.length) result +=
|
|
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
|
-
|
|
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
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
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',
|
|
509
|
-
const payload = _concat(
|
|
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
|
|
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;
|