nodestack-client 1.3.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +451 -341
- package/dist/Client.d.ts +11 -0
- package/dist/Client.d.ts.map +1 -1
- package/dist/auth/BaseAuthStore.d.ts.map +1 -1
- package/dist/auth/LocalAuthStore.d.ts.map +1 -1
- package/dist/auth/PkceUtil.d.ts +9 -0
- package/dist/auth/PkceUtil.d.ts.map +1 -0
- package/dist/errors/ClientResponseError.d.ts.map +1 -1
- package/dist/index.cjs +417 -8
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +417 -8
- package/dist/index.mjs.map +3 -3
- package/dist/services/AdminService.d.ts.map +1 -1
- package/dist/services/AppService.d.ts +30 -0
- package/dist/services/AppService.d.ts.map +1 -0
- package/dist/services/BaseService.d.ts +1 -0
- package/dist/services/BaseService.d.ts.map +1 -1
- package/dist/services/CollectionService.d.ts +2 -0
- package/dist/services/CollectionService.d.ts.map +1 -1
- package/dist/services/DemoService.d.ts.map +1 -1
- package/dist/services/FileService.d.ts +1 -0
- package/dist/services/FileService.d.ts.map +1 -1
- package/dist/services/RealtimeService.d.ts.map +1 -1
- package/dist/services/RecordService.d.ts +18 -1
- package/dist/services/RecordService.d.ts.map +1 -1
- package/dist/services/SnapshotService.d.ts +38 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/WebhookService.d.ts.map +1 -1
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -340,6 +340,175 @@ var RecordService = class extends BaseService {
|
|
|
340
340
|
}
|
|
341
341
|
return result;
|
|
342
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Returns a list of configured and enabled OAuth2 authentication providers.
|
|
345
|
+
*/
|
|
346
|
+
async listAuthMethods(redirectUri) {
|
|
347
|
+
return this.send(
|
|
348
|
+
`/api/collections/${encodeURIComponent(this.collectionName)}/auth-methods`,
|
|
349
|
+
{
|
|
350
|
+
method: "GET",
|
|
351
|
+
query: redirectUri ? { redirectUri } : void 0
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Authenticates against an auth collection using OAuth2 with PKCE code exchange.
|
|
357
|
+
* On success, automatically persists the token and record to authStore.
|
|
358
|
+
*/
|
|
359
|
+
async authWithOAuth2(options) {
|
|
360
|
+
let code = options.code;
|
|
361
|
+
let codeVerifier = options.codeVerifier;
|
|
362
|
+
if ((options.urlCallback || options.popup || typeof window !== "undefined" && !code && !codeVerifier) && (!code || !codeVerifier)) {
|
|
363
|
+
const authMethods = await this.listAuthMethods(options.redirectUrl);
|
|
364
|
+
const provider = authMethods.authProviders.find(
|
|
365
|
+
(p) => p.name.toLowerCase() === options.provider.toLowerCase()
|
|
366
|
+
);
|
|
367
|
+
if (!provider) {
|
|
368
|
+
throw new Error(`OAuth provider '${options.provider}' is not enabled or configured.`);
|
|
369
|
+
}
|
|
370
|
+
codeVerifier = provider.codeVerifier;
|
|
371
|
+
const expectedState = provider.state;
|
|
372
|
+
let expectedOrigin = null;
|
|
373
|
+
if (typeof window !== "undefined" && window.location) {
|
|
374
|
+
if (options.redirectUrl) {
|
|
375
|
+
try {
|
|
376
|
+
expectedOrigin = new URL(options.redirectUrl, window.location.href).origin;
|
|
377
|
+
} catch {
|
|
378
|
+
expectedOrigin = window.location.origin;
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
expectedOrigin = window.location.origin;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
let popup = options.popup || null;
|
|
385
|
+
if (options.urlCallback) {
|
|
386
|
+
const cbResult = options.urlCallback(provider.authUrl);
|
|
387
|
+
if (!popup && cbResult && typeof cbResult.postMessage === "function") {
|
|
388
|
+
popup = cbResult;
|
|
389
|
+
}
|
|
390
|
+
} else if (typeof window !== "undefined" && typeof window.open === "function") {
|
|
391
|
+
popup = window.open(provider.authUrl, "_blank", "width=600,height=700");
|
|
392
|
+
if (!popup) {
|
|
393
|
+
throw new Error("Failed to open OAuth popup window. It may have been blocked by the browser.");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return new Promise((resolve, reject) => {
|
|
397
|
+
if (typeof window !== "undefined" && window.addEventListener) {
|
|
398
|
+
let pollTimer = null;
|
|
399
|
+
const cleanup = () => {
|
|
400
|
+
if (pollTimer) {
|
|
401
|
+
clearInterval(pollTimer);
|
|
402
|
+
pollTimer = null;
|
|
403
|
+
}
|
|
404
|
+
if (typeof window !== "undefined" && window.removeEventListener) {
|
|
405
|
+
window.removeEventListener("message", handler);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const handler = async (event) => {
|
|
409
|
+
if (!event.data || typeof event.data !== "object" || event.data.type !== "nodestack_oauth_callback") {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (expectedOrigin && event.origin !== expectedOrigin) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (popup) {
|
|
416
|
+
if (event.source !== popup) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
} else if (event.source === window) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (expectedState && event.data.state !== expectedState) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
cleanup();
|
|
426
|
+
if (popup && typeof popup.close === "function") {
|
|
427
|
+
try {
|
|
428
|
+
popup.close();
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (event.data.error) {
|
|
433
|
+
const errMsg = typeof event.data.error === "string" ? event.data.error : event.data.error.message || event.data.error_description || JSON.stringify(event.data.error);
|
|
434
|
+
return reject(new Error(errMsg));
|
|
435
|
+
}
|
|
436
|
+
if (!event.data.code) {
|
|
437
|
+
return reject(new Error("OAuth callback message is missing authorization code."));
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const res = await this.authWithOAuth2({
|
|
441
|
+
provider: options.provider,
|
|
442
|
+
code: event.data.code,
|
|
443
|
+
codeVerifier,
|
|
444
|
+
redirectUrl: options.redirectUrl,
|
|
445
|
+
createData: options.createData,
|
|
446
|
+
state: expectedState
|
|
447
|
+
});
|
|
448
|
+
resolve(res);
|
|
449
|
+
} catch (err) {
|
|
450
|
+
reject(err);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
window.addEventListener("message", handler);
|
|
454
|
+
if (popup) {
|
|
455
|
+
pollTimer = setInterval(() => {
|
|
456
|
+
if (popup && popup.closed) {
|
|
457
|
+
cleanup();
|
|
458
|
+
reject(new Error("The OAuth popup window was closed before completing authentication."));
|
|
459
|
+
}
|
|
460
|
+
}, 500);
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
reject(new Error("urlCallback requires code and codeVerifier in non-browser environments."));
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (!code || !codeVerifier) {
|
|
468
|
+
throw new Error("Both code and codeVerifier are required for OAuth2 authentication");
|
|
469
|
+
}
|
|
470
|
+
const result = await this.send(
|
|
471
|
+
`/api/collections/${encodeURIComponent(this.collectionName)}/auth-with-oauth2`,
|
|
472
|
+
{
|
|
473
|
+
method: "POST",
|
|
474
|
+
body: {
|
|
475
|
+
provider: options.provider,
|
|
476
|
+
code,
|
|
477
|
+
codeVerifier,
|
|
478
|
+
state: options.state,
|
|
479
|
+
redirectUrl: options.redirectUrl,
|
|
480
|
+
createData: options.createData
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
);
|
|
484
|
+
if (result && result.token) {
|
|
485
|
+
this.client.authStore.save(result.token, result.record);
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Lists external social identities linked to a specific record.
|
|
491
|
+
*/
|
|
492
|
+
async listExternalAuths(recordId) {
|
|
493
|
+
return this.send(
|
|
494
|
+
`/api/collections/${encodeURIComponent(this.collectionName)}/records/${encodeURIComponent(recordId)}/external-auths`,
|
|
495
|
+
{
|
|
496
|
+
method: "GET"
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Unlinks an external social identity from a record.
|
|
502
|
+
*/
|
|
503
|
+
async unlinkExternalAuth(recordId, provider) {
|
|
504
|
+
await this.send(
|
|
505
|
+
`/api/collections/${encodeURIComponent(this.collectionName)}/records/${encodeURIComponent(recordId)}/external-auths/${encodeURIComponent(provider)}`,
|
|
506
|
+
{
|
|
507
|
+
method: "DELETE"
|
|
508
|
+
}
|
|
509
|
+
);
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
343
512
|
/**
|
|
344
513
|
* Exports records from the collection as CSV or JSON.
|
|
345
514
|
*/
|
|
@@ -431,7 +600,8 @@ var FileService = class extends BaseService {
|
|
|
431
600
|
const cleanCol = encodeURIComponent(collectionName);
|
|
432
601
|
const cleanId = encodeURIComponent(recordId);
|
|
433
602
|
const cleanFile = encodeURIComponent(filename);
|
|
434
|
-
|
|
603
|
+
const appSlug = options.app || this.client.appSlug;
|
|
604
|
+
let url = appSlug && appSlug !== "default" ? `${cleanBase}/api/apps/${encodeURIComponent(appSlug)}/files/${cleanCol}/${cleanId}/${cleanFile}` : `${cleanBase}/api/files/${cleanCol}/${cleanId}/${cleanFile}`;
|
|
435
605
|
const queryParts = [];
|
|
436
606
|
if (options.download) {
|
|
437
607
|
queryParts.push("download=1");
|
|
@@ -504,8 +674,16 @@ var RealtimeService = class extends BaseService {
|
|
|
504
674
|
const EventSourceClass = this.getEventSourceConstructor();
|
|
505
675
|
const base = (this.client.baseUrl || "").replace(/\/+$/, "");
|
|
506
676
|
let url = `${base}/api/realtime`;
|
|
677
|
+
const queryParams = new URLSearchParams();
|
|
507
678
|
if (this.client.authStore.token) {
|
|
508
|
-
|
|
679
|
+
queryParams.append("token", this.client.authStore.token);
|
|
680
|
+
}
|
|
681
|
+
if (this.client.appSlug) {
|
|
682
|
+
queryParams.append("app", this.client.appSlug);
|
|
683
|
+
}
|
|
684
|
+
const qs = queryParams.toString();
|
|
685
|
+
if (qs) {
|
|
686
|
+
url += `?${qs}`;
|
|
509
687
|
}
|
|
510
688
|
this.eventSource = new EventSourceClass(url);
|
|
511
689
|
const handleConnect = async (event) => {
|
|
@@ -737,6 +915,8 @@ var AdminService = class extends BaseService {
|
|
|
737
915
|
|
|
738
916
|
// src/services/CollectionService.ts
|
|
739
917
|
var CollectionService = class extends BaseService {
|
|
918
|
+
list = this.getList.bind(this);
|
|
919
|
+
get = this.getOne.bind(this);
|
|
740
920
|
/**
|
|
741
921
|
* Retrieves all collection schema models.
|
|
742
922
|
*/
|
|
@@ -843,11 +1023,72 @@ var DemoService = class extends BaseService {
|
|
|
843
1023
|
}
|
|
844
1024
|
};
|
|
845
1025
|
|
|
1026
|
+
// src/services/SnapshotService.ts
|
|
1027
|
+
var SnapshotService = class extends BaseService {
|
|
1028
|
+
/**
|
|
1029
|
+
* Retrieves all snapshots, active baseline, and live drift information.
|
|
1030
|
+
*/
|
|
1031
|
+
async list() {
|
|
1032
|
+
return this.send("/api/snapshots", {
|
|
1033
|
+
method: "GET"
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Alias for list() to retrieve status.
|
|
1038
|
+
*/
|
|
1039
|
+
async getStatus() {
|
|
1040
|
+
return this.list();
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Creates a new application state snapshot.
|
|
1044
|
+
* If snapshot limit (10) is reached, evictOldest: true can be passed to confirm eviction of the oldest.
|
|
1045
|
+
*/
|
|
1046
|
+
async create(options) {
|
|
1047
|
+
return this.send("/api/snapshots", {
|
|
1048
|
+
method: "POST",
|
|
1049
|
+
body: options || {}
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Retrieves a specific snapshot by ID.
|
|
1054
|
+
*/
|
|
1055
|
+
async get(id) {
|
|
1056
|
+
return this.send(`/api/snapshots/${encodeURIComponent(id)}`, {
|
|
1057
|
+
method: "GET"
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Restores application state to a specific snapshot.
|
|
1062
|
+
* Note: Future snapshots are NOT deleted; history remains intact.
|
|
1063
|
+
*/
|
|
1064
|
+
async restore(id) {
|
|
1065
|
+
return this.send(`/api/snapshots/${encodeURIComponent(id)}/restore`, {
|
|
1066
|
+
method: "POST"
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Deletes a specific snapshot by ID.
|
|
1071
|
+
*/
|
|
1072
|
+
async delete(id) {
|
|
1073
|
+
return this.send(`/api/snapshots/${encodeURIComponent(id)}`, {
|
|
1074
|
+
method: "DELETE"
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Clears all saved snapshots.
|
|
1079
|
+
*/
|
|
1080
|
+
async clear() {
|
|
1081
|
+
return this.send("/api/snapshots", {
|
|
1082
|
+
method: "DELETE"
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
|
|
846
1087
|
// src/services/WebhookService.ts
|
|
847
1088
|
function computeWebhookSignature(payload, secret) {
|
|
848
1089
|
const str = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
849
1090
|
try {
|
|
850
|
-
const nodeCrypto = typeof __require !== "undefined" ? __require("crypto") : globalThis?.crypto;
|
|
1091
|
+
const nodeCrypto = typeof __require !== "undefined" ? __require("crypto") : typeof globalThis !== "undefined" ? globalThis.require?.("crypto") || globalThis.crypto : null;
|
|
851
1092
|
if (nodeCrypto && typeof nodeCrypto.createHmac === "function") {
|
|
852
1093
|
return nodeCrypto.createHmac("sha256", secret).update(str, "utf8").digest("hex");
|
|
853
1094
|
}
|
|
@@ -871,9 +1112,10 @@ function verifyWebhookSignature(payload, signatureHeader, secret) {
|
|
|
871
1112
|
}
|
|
872
1113
|
if (cleanHeader.length !== expected.length) return false;
|
|
873
1114
|
try {
|
|
874
|
-
const nodeCrypto = typeof __require !== "undefined" ? __require("crypto") : null;
|
|
875
|
-
|
|
876
|
-
|
|
1115
|
+
const nodeCrypto = typeof __require !== "undefined" ? __require("crypto") : typeof globalThis !== "undefined" ? globalThis.require?.("crypto") : null;
|
|
1116
|
+
const buf = typeof Buffer !== "undefined" ? Buffer : typeof globalThis !== "undefined" ? globalThis.Buffer : null;
|
|
1117
|
+
if (nodeCrypto && typeof nodeCrypto.timingSafeEqual === "function" && buf) {
|
|
1118
|
+
return nodeCrypto.timingSafeEqual(buf.from(cleanHeader, "hex"), buf.from(expected, "hex"));
|
|
877
1119
|
}
|
|
878
1120
|
} catch {
|
|
879
1121
|
}
|
|
@@ -1000,16 +1242,70 @@ var WebhookService = class extends BaseService {
|
|
|
1000
1242
|
static computeSignature = computeWebhookSignature;
|
|
1001
1243
|
};
|
|
1002
1244
|
|
|
1245
|
+
// src/services/AppService.ts
|
|
1246
|
+
var AppService = class extends BaseService {
|
|
1247
|
+
list = this.getList.bind(this);
|
|
1248
|
+
get = this.getOne.bind(this);
|
|
1249
|
+
/**
|
|
1250
|
+
* Retrieves all configured applications.
|
|
1251
|
+
*/
|
|
1252
|
+
async getList() {
|
|
1253
|
+
const res = await this.send("/api/apps", {
|
|
1254
|
+
method: "GET"
|
|
1255
|
+
});
|
|
1256
|
+
return Array.isArray(res) ? res : res?.items || [];
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Retrieves a single application by ID or slug.
|
|
1260
|
+
*/
|
|
1261
|
+
async getOne(appId) {
|
|
1262
|
+
return this.send(`/api/apps/${encodeURIComponent(appId)}`, {
|
|
1263
|
+
method: "GET"
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Creates a new application.
|
|
1268
|
+
*/
|
|
1269
|
+
async create(data) {
|
|
1270
|
+
return this.send("/api/apps", {
|
|
1271
|
+
method: "POST",
|
|
1272
|
+
body: data
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Updates an existing application.
|
|
1277
|
+
*/
|
|
1278
|
+
async update(appId, data) {
|
|
1279
|
+
return this.send(`/api/apps/${encodeURIComponent(appId)}`, {
|
|
1280
|
+
method: "PATCH",
|
|
1281
|
+
body: data
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Deletes an application by ID or slug.
|
|
1286
|
+
*/
|
|
1287
|
+
async delete(appId) {
|
|
1288
|
+
await this.send(`/api/apps/${encodeURIComponent(appId)}`, {
|
|
1289
|
+
method: "DELETE"
|
|
1290
|
+
});
|
|
1291
|
+
return true;
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
|
|
1003
1295
|
// src/Client.ts
|
|
1004
|
-
var NodeStackClient = class {
|
|
1296
|
+
var NodeStackClient = class _NodeStackClient {
|
|
1005
1297
|
baseUrl;
|
|
1006
1298
|
authStore;
|
|
1299
|
+
apiKey;
|
|
1007
1300
|
files;
|
|
1008
1301
|
realtime;
|
|
1009
1302
|
admins;
|
|
1010
1303
|
collections;
|
|
1304
|
+
snapshots;
|
|
1011
1305
|
demo;
|
|
1012
1306
|
webhooks;
|
|
1307
|
+
apps;
|
|
1308
|
+
appSlug;
|
|
1013
1309
|
customFetch;
|
|
1014
1310
|
defaultHeaders;
|
|
1015
1311
|
defaultTimeout;
|
|
@@ -1018,16 +1314,20 @@ var NodeStackClient = class {
|
|
|
1018
1314
|
constructor(baseUrl = "/", options = {}) {
|
|
1019
1315
|
this.baseUrl = (baseUrl || "/").replace(/\/+$/, "");
|
|
1020
1316
|
this.authStore = options.authStore || new LocalAuthStore();
|
|
1317
|
+
this.apiKey = options.apiKey;
|
|
1021
1318
|
this.customFetch = options.fetch;
|
|
1022
1319
|
this.defaultHeaders = options.headers || {};
|
|
1023
1320
|
this.defaultTimeout = options.timeout || 12e4;
|
|
1024
1321
|
this.chaosOptions = options.chaos ? { ...options.chaos } : null;
|
|
1322
|
+
this.appSlug = options.app;
|
|
1025
1323
|
this.files = new FileService(this);
|
|
1026
1324
|
this.realtime = new RealtimeService(this, options.EventSource);
|
|
1027
1325
|
this.admins = new AdminService(this);
|
|
1028
1326
|
this.collections = new CollectionService(this);
|
|
1327
|
+
this.snapshots = new SnapshotService(this);
|
|
1029
1328
|
this.demo = new DemoService(this);
|
|
1030
1329
|
this.webhooks = new WebhookService(this);
|
|
1330
|
+
this.apps = new AppService(this);
|
|
1031
1331
|
}
|
|
1032
1332
|
/**
|
|
1033
1333
|
* Dynamically configure latency & chaos simulation options for all future requests.
|
|
@@ -1078,9 +1378,14 @@ var NodeStackClient = class {
|
|
|
1078
1378
|
...this.defaultHeaders,
|
|
1079
1379
|
...options.headers || {}
|
|
1080
1380
|
};
|
|
1081
|
-
if (this.
|
|
1381
|
+
if (this.apiKey && !headers["Authorization"] && !headers["authorization"] && !headers["x-api-key"] && !headers["X-API-Key"]) {
|
|
1382
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
1383
|
+
} else if (this.authStore.token && !headers["Authorization"] && !headers["authorization"]) {
|
|
1082
1384
|
headers["Authorization"] = `Bearer ${this.authStore.token}`;
|
|
1083
1385
|
}
|
|
1386
|
+
if (this.appSlug && !headers["x-nodestack-app"] && !headers["X-NodeStack-App"]) {
|
|
1387
|
+
headers["x-nodestack-app"] = this.appSlug;
|
|
1388
|
+
}
|
|
1084
1389
|
const effectiveChaos = {
|
|
1085
1390
|
...this.chaosOptions || {},
|
|
1086
1391
|
...options.chaos || {},
|
|
@@ -1197,14 +1502,116 @@ var NodeStackClient = class {
|
|
|
1197
1502
|
async metrics() {
|
|
1198
1503
|
return this.send("/api/metrics", { method: "GET" });
|
|
1199
1504
|
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Returns a new NodeStackClient instance scoped to a specific application/MVP.
|
|
1507
|
+
* Inherits baseUrl, authStore, customFetch, defaultHeaders, and defaultTimeout.
|
|
1508
|
+
*/
|
|
1509
|
+
app(slug, options = {}) {
|
|
1510
|
+
return new _NodeStackClient(this.baseUrl, {
|
|
1511
|
+
authStore: options.authStore || this.authStore,
|
|
1512
|
+
fetch: options.fetch || this.customFetch,
|
|
1513
|
+
headers: { ...this.defaultHeaders, ...options.headers || {} },
|
|
1514
|
+
timeout: options.timeout !== void 0 ? options.timeout : this.defaultTimeout,
|
|
1515
|
+
chaos: options.chaos !== void 0 ? options.chaos ? { ...options.chaos } : void 0 : this.chaosOptions ? { ...this.chaosOptions } : void 0,
|
|
1516
|
+
app: slug,
|
|
1517
|
+
EventSource: options.EventSource || this.realtime.customEventSource
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1200
1520
|
};
|
|
1201
1521
|
|
|
1202
1522
|
// src/auth/MemoryAuthStore.ts
|
|
1203
1523
|
var MemoryAuthStore = class extends BaseAuthStore {
|
|
1204
1524
|
// Inherits default in-memory behavior of BaseAuthStore
|
|
1205
1525
|
};
|
|
1526
|
+
|
|
1527
|
+
// src/auth/PkceUtil.ts
|
|
1528
|
+
var PkceUtil = class _PkceUtil {
|
|
1529
|
+
static generateVerifier(length = 64) {
|
|
1530
|
+
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
1531
|
+
const clampedLength = Math.max(43, Math.min(128, length));
|
|
1532
|
+
const randomBytes = new Uint8Array(clampedLength);
|
|
1533
|
+
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
1534
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
1535
|
+
crypto.getRandomValues(randomBytes);
|
|
1536
|
+
} else if (g.crypto && g.crypto.getRandomValues) {
|
|
1537
|
+
g.crypto.getRandomValues(randomBytes);
|
|
1538
|
+
} else {
|
|
1539
|
+
let filled = false;
|
|
1540
|
+
try {
|
|
1541
|
+
const req = g.require;
|
|
1542
|
+
if (typeof req === "function") {
|
|
1543
|
+
req("crypto").randomFillSync(randomBytes);
|
|
1544
|
+
filled = true;
|
|
1545
|
+
}
|
|
1546
|
+
} catch {
|
|
1547
|
+
}
|
|
1548
|
+
if (!filled) {
|
|
1549
|
+
throw new Error(
|
|
1550
|
+
"A cryptographically secure random number generator (crypto.getRandomValues or node:crypto) is required to generate a PKCE code_verifier."
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
let result = "";
|
|
1555
|
+
for (let i = 0; i < clampedLength; i++) {
|
|
1556
|
+
result += charset[randomBytes[i] % charset.length];
|
|
1557
|
+
}
|
|
1558
|
+
return result;
|
|
1559
|
+
}
|
|
1560
|
+
static async generateChallenge(verifier) {
|
|
1561
|
+
const encoder = new TextEncoder();
|
|
1562
|
+
const data = encoder.encode(verifier);
|
|
1563
|
+
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
1564
|
+
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
1565
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
1566
|
+
return _PkceUtil.base64UrlEncode(new Uint8Array(hash));
|
|
1567
|
+
} else if (g.crypto && g.crypto.subtle) {
|
|
1568
|
+
const hash = await g.crypto.subtle.digest("SHA-256", data);
|
|
1569
|
+
return _PkceUtil.base64UrlEncode(new Uint8Array(hash));
|
|
1570
|
+
}
|
|
1571
|
+
try {
|
|
1572
|
+
const req = g.require;
|
|
1573
|
+
if (typeof req === "function") {
|
|
1574
|
+
const hash = req("crypto").createHash("sha256").update(data).digest();
|
|
1575
|
+
return _PkceUtil.base64UrlEncode(new Uint8Array(hash));
|
|
1576
|
+
}
|
|
1577
|
+
} catch {
|
|
1578
|
+
}
|
|
1579
|
+
throw new Error("Web Crypto API (crypto.subtle) is not available to compute SHA-256 code challenge.");
|
|
1580
|
+
}
|
|
1581
|
+
static base64UrlEncode(bytes) {
|
|
1582
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1583
|
+
let base64 = "";
|
|
1584
|
+
const byteLength = bytes.byteLength;
|
|
1585
|
+
const byteRemainder = byteLength % 3;
|
|
1586
|
+
const mainLength = byteLength - byteRemainder;
|
|
1587
|
+
let a, b, c, d;
|
|
1588
|
+
let chunk;
|
|
1589
|
+
for (let i = 0; i < mainLength; i += 3) {
|
|
1590
|
+
chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
1591
|
+
a = (chunk & 16515072) >> 18;
|
|
1592
|
+
b = (chunk & 258048) >> 12;
|
|
1593
|
+
c = (chunk & 4032) >> 6;
|
|
1594
|
+
d = chunk & 63;
|
|
1595
|
+
base64 += chars[a] + chars[b] + chars[c] + chars[d];
|
|
1596
|
+
}
|
|
1597
|
+
if (byteRemainder === 1) {
|
|
1598
|
+
chunk = bytes[mainLength];
|
|
1599
|
+
a = (chunk & 252) >> 2;
|
|
1600
|
+
b = (chunk & 3) << 4;
|
|
1601
|
+
base64 += chars[a] + chars[b] + "==";
|
|
1602
|
+
} else if (byteRemainder === 2) {
|
|
1603
|
+
chunk = bytes[mainLength] << 8 | bytes[mainLength + 1];
|
|
1604
|
+
a = (chunk & 64512) >> 10;
|
|
1605
|
+
b = (chunk & 1008) >> 4;
|
|
1606
|
+
c = (chunk & 15) << 2;
|
|
1607
|
+
base64 += chars[a] + chars[b] + chars[c] + "=";
|
|
1608
|
+
}
|
|
1609
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1206
1612
|
export {
|
|
1207
1613
|
AdminService,
|
|
1614
|
+
AppService,
|
|
1208
1615
|
BaseAuthStore,
|
|
1209
1616
|
BaseService,
|
|
1210
1617
|
ClientResponseError,
|
|
@@ -1214,8 +1621,10 @@ export {
|
|
|
1214
1621
|
LocalAuthStore,
|
|
1215
1622
|
MemoryAuthStore,
|
|
1216
1623
|
NodeStackClient,
|
|
1624
|
+
PkceUtil,
|
|
1217
1625
|
RealtimeService,
|
|
1218
1626
|
RecordService,
|
|
1627
|
+
SnapshotService,
|
|
1219
1628
|
WebhookService,
|
|
1220
1629
|
computeWebhookSignature,
|
|
1221
1630
|
decodeJwtPayload,
|