nodestack-client 2.0.0 → 2.2.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 +52 -0
- package/dist/Client.d.ts +1 -0
- package/dist/Client.d.ts.map +1 -1
- package/dist/auth/PkceUtil.d.ts +9 -0
- package/dist/auth/PkceUtil.d.ts.map +1 -0
- package/dist/index.cjs +261 -3
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +261 -3
- package/dist/index.mjs.map +3 -3
- package/dist/services/RecordService.d.ts +18 -1
- package/dist/services/RecordService.d.ts.map +1 -1
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +50 -50
package/README.md
CHANGED
|
@@ -68,6 +68,58 @@ console.log('JWT Token:', client.authStore.token);
|
|
|
68
68
|
console.log('Is valid:', client.authStore.isValid);
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
### Authenticate via OAuth2 / Social Login (PKCE)
|
|
72
|
+
NodeStack supports native PKCE code exchange for Google, GitHub, Apple, Discord, and Microsoft:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// 1. Fetch available providers and precomputed PKCE challenge & state
|
|
76
|
+
const { authProviders } = await client.collection('users').listAuthMethods('http://localhost:3000/callback');
|
|
77
|
+
const google = authProviders.find((p) => p.name === 'google');
|
|
78
|
+
|
|
79
|
+
// 2. Redirect user or open popup with google.authUrl
|
|
80
|
+
// window.location.href = google.authUrl;
|
|
81
|
+
|
|
82
|
+
// 3. On your callback page (/callback), exchange code and codeVerifier
|
|
83
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
84
|
+
const code = urlParams.get('code');
|
|
85
|
+
|
|
86
|
+
const authData = await client.collection('users').authWithOAuth2({
|
|
87
|
+
provider: 'google',
|
|
88
|
+
code: code!,
|
|
89
|
+
codeVerifier: google.codeVerifier,
|
|
90
|
+
state: google.state,
|
|
91
|
+
redirectUrl: 'http://localhost:3000/callback',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log('Logged in via Google:', authData.record);
|
|
95
|
+
console.log('Is newly registered user:', authData.meta.isNew);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### One-Step Popup Authentication (Single-Page Apps)
|
|
99
|
+
```typescript
|
|
100
|
+
// nodestack-client verifies popup origin, window source, and binds CSRF state automatically:
|
|
101
|
+
const authData = await client.collection('users').authWithOAuth2({
|
|
102
|
+
provider: 'google',
|
|
103
|
+
urlCallback: (url) => window.open(url, '_blank', 'width=500,height=650'),
|
|
104
|
+
redirectUrl: 'http://localhost:3000/callback',
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Machine-to-Machine (M2M) Authentication with API Keys
|
|
109
|
+
For background workers, cron daemons, serverless scripts, and CI/CD pipelines:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { NodeStackClient } from 'nodestack-client';
|
|
113
|
+
|
|
114
|
+
// Initialized with a scoped API key (e.g. "orders:*", "products:read")
|
|
115
|
+
const client = new NodeStackClient('https://api.yourdomain.com', {
|
|
116
|
+
apiKey: process.env.NODESTACK_API_KEY, // 'nsk_live_...'
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Requests automatically inject Authorization: Bearer nsk_...
|
|
120
|
+
const orders = await client.collection('orders').getList(1, 20);
|
|
121
|
+
```
|
|
122
|
+
|
|
71
123
|
### Authenticate as a Superuser / Admin
|
|
72
124
|
```typescript
|
|
73
125
|
const adminAuth = await client.admins.authWithPassword('admin@nodestack.io', 'MySecretAdminPassword');
|
package/dist/Client.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { ClientOptions, SendOptions, RecordModel, ChaosOptions } from './ty
|
|
|
15
15
|
export declare class NodeStackClient<TCollections extends Record<string, any> = Record<string, RecordModel>> {
|
|
16
16
|
readonly baseUrl: string;
|
|
17
17
|
readonly authStore: BaseAuthStore;
|
|
18
|
+
apiKey?: string;
|
|
18
19
|
readonly files: FileService;
|
|
19
20
|
readonly realtime: RealtimeService;
|
|
20
21
|
readonly admins: AdminService;
|
package/dist/Client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Client.d.ts","sourceRoot":"","sources":["../src/Client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,WAAW,EACX,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,qBAAa,eAAe,CAAC,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;IACjG,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,SAAgB,SAAS,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"Client.d.ts","sourceRoot":"","sources":["../src/Client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight, zero-dependency PKCE helper for browsers and Node.js environments.
|
|
3
|
+
*/
|
|
4
|
+
export declare class PkceUtil {
|
|
5
|
+
static generateVerifier(length?: number): string;
|
|
6
|
+
static generateChallenge(verifier: string): Promise<string>;
|
|
7
|
+
static base64UrlEncode(bytes: Uint8Array): string;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=PkceUtil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PkceUtil.d.ts","sourceRoot":"","sources":["../../src/auth/PkceUtil.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAc,gBAAgB,CAAC,MAAM,SAAK,GAAG,MAAM,CAgClD;IAED,OAAoB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBvE;IAED,OAAc,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAiCvD;CACF"}
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(index_exports, {
|
|
|
31
31
|
LocalAuthStore: () => LocalAuthStore,
|
|
32
32
|
MemoryAuthStore: () => MemoryAuthStore,
|
|
33
33
|
NodeStackClient: () => NodeStackClient,
|
|
34
|
+
PkceUtil: () => PkceUtil,
|
|
34
35
|
RealtimeService: () => RealtimeService,
|
|
35
36
|
RecordService: () => RecordService,
|
|
36
37
|
SnapshotService: () => SnapshotService,
|
|
@@ -233,7 +234,6 @@ var BaseService = class {
|
|
|
233
234
|
constructor(client) {
|
|
234
235
|
this.client = client;
|
|
235
236
|
}
|
|
236
|
-
client;
|
|
237
237
|
send(path, options = {}) {
|
|
238
238
|
return this.client.send(path, options);
|
|
239
239
|
}
|
|
@@ -245,7 +245,6 @@ var RecordService = class extends BaseService {
|
|
|
245
245
|
super(client);
|
|
246
246
|
this.collectionName = collectionName;
|
|
247
247
|
}
|
|
248
|
-
collectionName;
|
|
249
248
|
get clientWithRealtime() {
|
|
250
249
|
return this.client;
|
|
251
250
|
}
|
|
@@ -377,6 +376,175 @@ var RecordService = class extends BaseService {
|
|
|
377
376
|
}
|
|
378
377
|
return result;
|
|
379
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Returns a list of configured and enabled OAuth2 authentication providers.
|
|
381
|
+
*/
|
|
382
|
+
async listAuthMethods(redirectUri) {
|
|
383
|
+
return this.send(
|
|
384
|
+
`/api/collections/${encodeURIComponent(this.collectionName)}/auth-methods`,
|
|
385
|
+
{
|
|
386
|
+
method: "GET",
|
|
387
|
+
query: redirectUri ? { redirectUri } : void 0
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Authenticates against an auth collection using OAuth2 with PKCE code exchange.
|
|
393
|
+
* On success, automatically persists the token and record to authStore.
|
|
394
|
+
*/
|
|
395
|
+
async authWithOAuth2(options) {
|
|
396
|
+
let code = options.code;
|
|
397
|
+
let codeVerifier = options.codeVerifier;
|
|
398
|
+
if ((options.urlCallback || options.popup || typeof window !== "undefined" && !code && !codeVerifier) && (!code || !codeVerifier)) {
|
|
399
|
+
const authMethods = await this.listAuthMethods(options.redirectUrl);
|
|
400
|
+
const provider = authMethods.authProviders.find(
|
|
401
|
+
(p) => p.name.toLowerCase() === options.provider.toLowerCase()
|
|
402
|
+
);
|
|
403
|
+
if (!provider) {
|
|
404
|
+
throw new Error(`OAuth provider '${options.provider}' is not enabled or configured.`);
|
|
405
|
+
}
|
|
406
|
+
codeVerifier = provider.codeVerifier;
|
|
407
|
+
const expectedState = provider.state;
|
|
408
|
+
let expectedOrigin = null;
|
|
409
|
+
if (typeof window !== "undefined" && window.location) {
|
|
410
|
+
if (options.redirectUrl) {
|
|
411
|
+
try {
|
|
412
|
+
expectedOrigin = new URL(options.redirectUrl, window.location.href).origin;
|
|
413
|
+
} catch {
|
|
414
|
+
expectedOrigin = window.location.origin;
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
expectedOrigin = window.location.origin;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
let popup = options.popup || null;
|
|
421
|
+
if (options.urlCallback) {
|
|
422
|
+
const cbResult = options.urlCallback(provider.authUrl);
|
|
423
|
+
if (!popup && cbResult && typeof cbResult.postMessage === "function") {
|
|
424
|
+
popup = cbResult;
|
|
425
|
+
}
|
|
426
|
+
} else if (typeof window !== "undefined" && typeof window.open === "function") {
|
|
427
|
+
popup = window.open(provider.authUrl, "_blank", "width=600,height=700");
|
|
428
|
+
if (!popup) {
|
|
429
|
+
throw new Error("Failed to open OAuth popup window. It may have been blocked by the browser.");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return new Promise((resolve, reject) => {
|
|
433
|
+
if (typeof window !== "undefined" && window.addEventListener) {
|
|
434
|
+
let pollTimer = null;
|
|
435
|
+
const cleanup = () => {
|
|
436
|
+
if (pollTimer) {
|
|
437
|
+
clearInterval(pollTimer);
|
|
438
|
+
pollTimer = null;
|
|
439
|
+
}
|
|
440
|
+
if (typeof window !== "undefined" && window.removeEventListener) {
|
|
441
|
+
window.removeEventListener("message", handler);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
const handler = async (event) => {
|
|
445
|
+
if (!event.data || typeof event.data !== "object" || event.data.type !== "nodestack_oauth_callback") {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (expectedOrigin && event.origin !== expectedOrigin) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (popup) {
|
|
452
|
+
if (event.source !== popup) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
} else if (event.source === window) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (expectedState && event.data.state !== expectedState) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
cleanup();
|
|
462
|
+
if (popup && typeof popup.close === "function") {
|
|
463
|
+
try {
|
|
464
|
+
popup.close();
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (event.data.error) {
|
|
469
|
+
const errMsg = typeof event.data.error === "string" ? event.data.error : event.data.error.message || event.data.error_description || JSON.stringify(event.data.error);
|
|
470
|
+
return reject(new Error(errMsg));
|
|
471
|
+
}
|
|
472
|
+
if (!event.data.code) {
|
|
473
|
+
return reject(new Error("OAuth callback message is missing authorization code."));
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
const res = await this.authWithOAuth2({
|
|
477
|
+
provider: options.provider,
|
|
478
|
+
code: event.data.code,
|
|
479
|
+
codeVerifier,
|
|
480
|
+
redirectUrl: options.redirectUrl,
|
|
481
|
+
createData: options.createData,
|
|
482
|
+
state: expectedState
|
|
483
|
+
});
|
|
484
|
+
resolve(res);
|
|
485
|
+
} catch (err) {
|
|
486
|
+
reject(err);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
window.addEventListener("message", handler);
|
|
490
|
+
if (popup) {
|
|
491
|
+
pollTimer = setInterval(() => {
|
|
492
|
+
if (popup && popup.closed) {
|
|
493
|
+
cleanup();
|
|
494
|
+
reject(new Error("The OAuth popup window was closed before completing authentication."));
|
|
495
|
+
}
|
|
496
|
+
}, 500);
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
reject(new Error("urlCallback requires code and codeVerifier in non-browser environments."));
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
if (!code || !codeVerifier) {
|
|
504
|
+
throw new Error("Both code and codeVerifier are required for OAuth2 authentication");
|
|
505
|
+
}
|
|
506
|
+
const result = await this.send(
|
|
507
|
+
`/api/collections/${encodeURIComponent(this.collectionName)}/auth-with-oauth2`,
|
|
508
|
+
{
|
|
509
|
+
method: "POST",
|
|
510
|
+
body: {
|
|
511
|
+
provider: options.provider,
|
|
512
|
+
code,
|
|
513
|
+
codeVerifier,
|
|
514
|
+
state: options.state,
|
|
515
|
+
redirectUrl: options.redirectUrl,
|
|
516
|
+
createData: options.createData
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
);
|
|
520
|
+
if (result && result.token) {
|
|
521
|
+
this.client.authStore.save(result.token, result.record);
|
|
522
|
+
}
|
|
523
|
+
return result;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Lists external social identities linked to a specific record.
|
|
527
|
+
*/
|
|
528
|
+
async listExternalAuths(recordId) {
|
|
529
|
+
return this.send(
|
|
530
|
+
`/api/collections/${encodeURIComponent(this.collectionName)}/records/${encodeURIComponent(recordId)}/external-auths`,
|
|
531
|
+
{
|
|
532
|
+
method: "GET"
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Unlinks an external social identity from a record.
|
|
538
|
+
*/
|
|
539
|
+
async unlinkExternalAuth(recordId, provider) {
|
|
540
|
+
await this.send(
|
|
541
|
+
`/api/collections/${encodeURIComponent(this.collectionName)}/records/${encodeURIComponent(recordId)}/external-auths/${encodeURIComponent(provider)}`,
|
|
542
|
+
{
|
|
543
|
+
method: "DELETE"
|
|
544
|
+
}
|
|
545
|
+
);
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
380
548
|
/**
|
|
381
549
|
* Exports records from the collection as CSV or JSON.
|
|
382
550
|
*/
|
|
@@ -1164,6 +1332,7 @@ var AppService = class extends BaseService {
|
|
|
1164
1332
|
var NodeStackClient = class _NodeStackClient {
|
|
1165
1333
|
baseUrl;
|
|
1166
1334
|
authStore;
|
|
1335
|
+
apiKey;
|
|
1167
1336
|
files;
|
|
1168
1337
|
realtime;
|
|
1169
1338
|
admins;
|
|
@@ -1181,6 +1350,7 @@ var NodeStackClient = class _NodeStackClient {
|
|
|
1181
1350
|
constructor(baseUrl = "/", options = {}) {
|
|
1182
1351
|
this.baseUrl = (baseUrl || "/").replace(/\/+$/, "");
|
|
1183
1352
|
this.authStore = options.authStore || new LocalAuthStore();
|
|
1353
|
+
this.apiKey = options.apiKey;
|
|
1184
1354
|
this.customFetch = options.fetch;
|
|
1185
1355
|
this.defaultHeaders = options.headers || {};
|
|
1186
1356
|
this.defaultTimeout = options.timeout || 12e4;
|
|
@@ -1244,7 +1414,9 @@ var NodeStackClient = class _NodeStackClient {
|
|
|
1244
1414
|
...this.defaultHeaders,
|
|
1245
1415
|
...options.headers || {}
|
|
1246
1416
|
};
|
|
1247
|
-
if (this.
|
|
1417
|
+
if (this.apiKey && !headers["Authorization"] && !headers["authorization"] && !headers["x-api-key"] && !headers["X-API-Key"]) {
|
|
1418
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
1419
|
+
} else if (this.authStore.token && !headers["Authorization"] && !headers["authorization"]) {
|
|
1248
1420
|
headers["Authorization"] = `Bearer ${this.authStore.token}`;
|
|
1249
1421
|
}
|
|
1250
1422
|
if (this.appSlug && !headers["x-nodestack-app"] && !headers["X-NodeStack-App"]) {
|
|
@@ -1387,4 +1559,90 @@ var NodeStackClient = class _NodeStackClient {
|
|
|
1387
1559
|
var MemoryAuthStore = class extends BaseAuthStore {
|
|
1388
1560
|
// Inherits default in-memory behavior of BaseAuthStore
|
|
1389
1561
|
};
|
|
1562
|
+
|
|
1563
|
+
// src/auth/PkceUtil.ts
|
|
1564
|
+
var PkceUtil = class _PkceUtil {
|
|
1565
|
+
static generateVerifier(length = 64) {
|
|
1566
|
+
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
1567
|
+
const clampedLength = Math.max(43, Math.min(128, length));
|
|
1568
|
+
const randomBytes = new Uint8Array(clampedLength);
|
|
1569
|
+
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
1570
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
1571
|
+
crypto.getRandomValues(randomBytes);
|
|
1572
|
+
} else if (g.crypto && g.crypto.getRandomValues) {
|
|
1573
|
+
g.crypto.getRandomValues(randomBytes);
|
|
1574
|
+
} else {
|
|
1575
|
+
let filled = false;
|
|
1576
|
+
try {
|
|
1577
|
+
const req = g.require;
|
|
1578
|
+
if (typeof req === "function") {
|
|
1579
|
+
req("crypto").randomFillSync(randomBytes);
|
|
1580
|
+
filled = true;
|
|
1581
|
+
}
|
|
1582
|
+
} catch {
|
|
1583
|
+
}
|
|
1584
|
+
if (!filled) {
|
|
1585
|
+
throw new Error(
|
|
1586
|
+
"A cryptographically secure random number generator (crypto.getRandomValues or node:crypto) is required to generate a PKCE code_verifier."
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
let result = "";
|
|
1591
|
+
for (let i = 0; i < clampedLength; i++) {
|
|
1592
|
+
result += charset[randomBytes[i] % charset.length];
|
|
1593
|
+
}
|
|
1594
|
+
return result;
|
|
1595
|
+
}
|
|
1596
|
+
static async generateChallenge(verifier) {
|
|
1597
|
+
const encoder = new TextEncoder();
|
|
1598
|
+
const data = encoder.encode(verifier);
|
|
1599
|
+
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
1600
|
+
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
1601
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
1602
|
+
return _PkceUtil.base64UrlEncode(new Uint8Array(hash));
|
|
1603
|
+
} else if (g.crypto && g.crypto.subtle) {
|
|
1604
|
+
const hash = await g.crypto.subtle.digest("SHA-256", data);
|
|
1605
|
+
return _PkceUtil.base64UrlEncode(new Uint8Array(hash));
|
|
1606
|
+
}
|
|
1607
|
+
try {
|
|
1608
|
+
const req = g.require;
|
|
1609
|
+
if (typeof req === "function") {
|
|
1610
|
+
const hash = req("crypto").createHash("sha256").update(data).digest();
|
|
1611
|
+
return _PkceUtil.base64UrlEncode(new Uint8Array(hash));
|
|
1612
|
+
}
|
|
1613
|
+
} catch {
|
|
1614
|
+
}
|
|
1615
|
+
throw new Error("Web Crypto API (crypto.subtle) is not available to compute SHA-256 code challenge.");
|
|
1616
|
+
}
|
|
1617
|
+
static base64UrlEncode(bytes) {
|
|
1618
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1619
|
+
let base64 = "";
|
|
1620
|
+
const byteLength = bytes.byteLength;
|
|
1621
|
+
const byteRemainder = byteLength % 3;
|
|
1622
|
+
const mainLength = byteLength - byteRemainder;
|
|
1623
|
+
let a, b, c, d;
|
|
1624
|
+
let chunk;
|
|
1625
|
+
for (let i = 0; i < mainLength; i += 3) {
|
|
1626
|
+
chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
1627
|
+
a = (chunk & 16515072) >> 18;
|
|
1628
|
+
b = (chunk & 258048) >> 12;
|
|
1629
|
+
c = (chunk & 4032) >> 6;
|
|
1630
|
+
d = chunk & 63;
|
|
1631
|
+
base64 += chars[a] + chars[b] + chars[c] + chars[d];
|
|
1632
|
+
}
|
|
1633
|
+
if (byteRemainder === 1) {
|
|
1634
|
+
chunk = bytes[mainLength];
|
|
1635
|
+
a = (chunk & 252) >> 2;
|
|
1636
|
+
b = (chunk & 3) << 4;
|
|
1637
|
+
base64 += chars[a] + chars[b] + "==";
|
|
1638
|
+
} else if (byteRemainder === 2) {
|
|
1639
|
+
chunk = bytes[mainLength] << 8 | bytes[mainLength + 1];
|
|
1640
|
+
a = (chunk & 64512) >> 10;
|
|
1641
|
+
b = (chunk & 1008) >> 4;
|
|
1642
|
+
c = (chunk & 15) << 2;
|
|
1643
|
+
base64 += chars[a] + chars[b] + chars[c] + "=";
|
|
1644
|
+
}
|
|
1645
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1390
1648
|
//# sourceMappingURL=index.cjs.map
|