@explorins/pers-sdk-react-native 2.3.7 → 2.3.8
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/LICENSE +21 -0
- package/README.md +1266 -1244
- package/dist/__tests__/PersSDKProvider.test.js +33 -0
- package/dist/hooks/useBusiness.d.ts +2 -2
- package/dist/hooks/useBusiness.d.ts.map +1 -1
- package/dist/hooks/useBusiness.js +2 -2
- package/dist/hooks/useIPFSUrl.d.ts +3 -3
- package/dist/hooks/useIPFSUrl.d.ts.map +1 -1
- package/dist/hooks/useIPFSUrl.js +7 -7
- package/dist/hooks/useWeb3.js +4 -4
- package/dist/index.js +2400 -487
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +4 -0
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +14 -0
- package/dist/providers/react-native-http-client.d.ts +2 -0
- package/dist/providers/react-native-http-client.d.ts.map +1 -1
- package/dist/providers/react-native-http-client.js +5 -2
- package/dist/storage/rn-secure-storage.d.ts.map +1 -1
- package/dist/storage/rn-secure-storage.js +21 -8
- package/package.json +3 -3
- package/src/__tests__/PersSDKProvider.test.tsx +484 -420
- package/src/__tests__/setup.ts +40 -40
- package/src/hooks/index.ts +60 -60
- package/src/hooks/useAnalytics.ts +327 -327
- package/src/hooks/useAuth.ts +265 -265
- package/src/hooks/useBookings.ts +191 -191
- package/src/hooks/useBusiness.ts +251 -251
- package/src/hooks/useCampaigns.ts +500 -500
- package/src/hooks/useDonations.ts +66 -66
- package/src/hooks/useEvents.ts +372 -372
- package/src/hooks/useFiles.ts +155 -155
- package/src/hooks/useIPFSUrl.ts +192 -192
- package/src/hooks/usePurchases.ts +65 -65
- package/src/hooks/useRedemptions.ts +467 -467
- package/src/hooks/useTenants.ts +72 -72
- package/src/hooks/useTokenBalances.ts +385 -385
- package/src/hooks/useTokens.ts +313 -313
- package/src/hooks/useTransactionSigner.ts +611 -611
- package/src/hooks/useTransactions.ts +299 -299
- package/src/hooks/useTriggerSources.ts +301 -301
- package/src/hooks/useUserStatus.ts +61 -61
- package/src/hooks/useUsers.ts +167 -167
- package/src/hooks/useWeb3.ts +355 -355
- package/src/index.ts +777 -777
- package/src/polyfills/index.ts +160 -160
- package/src/polyfills/web3-polyfills.ts +97 -97
- package/src/providers/PersSDKProvider.tsx +393 -374
- package/src/providers/react-native-auth-provider.ts +110 -110
- package/src/providers/react-native-http-client.ts +150 -145
- package/src/providers/rn-dpop-provider.ts +190 -190
- package/src/storage/async-storage-token-storage.ts +185 -185
- package/src/storage/rn-secure-storage.ts +182 -170
- package/src/types/react-native-globals.d.ts +45 -45
- package/src/utils/index.ts +8 -8
- package/src/utils/platform-detection.ts +86 -86
- package/dist/index-CzLxqePZ.js +0 -7717
- package/dist/index-CzLxqePZ.js.map +0 -1
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
import { Platform } from 'react-native';
|
|
2
|
-
import { Buffer } from 'buffer';
|
|
3
|
-
import { DPoPCryptoProvider, DPoPKeyPair } from '@explorins/pers-sdk/core';
|
|
4
|
-
|
|
5
|
-
// Lazy-loaded crypto module - initialized on first use to avoid native module timing issues
|
|
6
|
-
let _crypto: any = null;
|
|
7
|
-
let _cryptoInitialized = false;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Lazily load the crypto module.
|
|
11
|
-
*
|
|
12
|
-
* Supports customer pre-initialization via global.__QuickCrypto for environments
|
|
13
|
-
* where native module timing is unpredictable (e.g., certain React Native setups).
|
|
14
|
-
*
|
|
15
|
-
* @returns The crypto module or null if unavailable
|
|
16
|
-
*/
|
|
17
|
-
function getCrypto(): any {
|
|
18
|
-
if (_cryptoInitialized) {
|
|
19
|
-
return _crypto;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
_cryptoInitialized = true;
|
|
23
|
-
|
|
24
|
-
if (Platform.OS === 'web') {
|
|
25
|
-
// On Web, we shouldn't be using this provider (Core SDK has WebDPoPProvider)
|
|
26
|
-
_crypto = {
|
|
27
|
-
generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); },
|
|
28
|
-
randomUUID: () => globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`
|
|
29
|
-
};
|
|
30
|
-
return _crypto;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Check for customer pre-initialized global (for native module timing issues)
|
|
34
|
-
if (typeof global !== 'undefined' && (global as any).__QuickCrypto) {
|
|
35
|
-
_crypto = (global as any).__QuickCrypto;
|
|
36
|
-
return _crypto;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Standard lazy load
|
|
40
|
-
try {
|
|
41
|
-
_crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
|
|
42
|
-
} catch (e) {
|
|
43
|
-
console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto.', e);
|
|
44
|
-
console.warn('Tip: If you encounter timing issues, pre-initialize in index.js: global.__QuickCrypto = require("react-native-quick-crypto")');
|
|
45
|
-
_crypto = null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return _crypto;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
|
|
52
|
-
/**
|
|
53
|
-
* Generates a new key pair (ES256 recommended)
|
|
54
|
-
*
|
|
55
|
-
* Uses WebCrypto API (crypto.subtle) for cross-platform compatibility.
|
|
56
|
-
* Falls back to Node.js crypto API on iOS if needed.
|
|
57
|
-
*/
|
|
58
|
-
async generateKeyPair(options?: { extractable?: boolean }): Promise<DPoPKeyPair> {
|
|
59
|
-
const crypto = getCrypto();
|
|
60
|
-
if (!crypto) {
|
|
61
|
-
throw new Error('Crypto module not available. Install react-native-quick-crypto or pre-initialize global.__QuickCrypto');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Try WebCrypto API first (works on both iOS and Android)
|
|
65
|
-
if (crypto.subtle) {
|
|
66
|
-
try {
|
|
67
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
68
|
-
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
69
|
-
true, // extractable - required for JWK export
|
|
70
|
-
['sign', 'verify']
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const publicKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
|
74
|
-
const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
publicKey: publicKeyJwk,
|
|
78
|
-
privateKey: privateKeyJwk
|
|
79
|
-
};
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.warn('[DPoP] WebCrypto API failed, trying Node.js crypto API', err);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Fallback: Node.js crypto API (works on iOS)
|
|
86
|
-
return new Promise((resolve, reject) => {
|
|
87
|
-
crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err: any, publicKey: any, privateKey: any) => {
|
|
88
|
-
if (err) return reject(err);
|
|
89
|
-
try {
|
|
90
|
-
const pJwk = publicKey.export({ format: 'jwk' });
|
|
91
|
-
const prJwk = privateKey.export({ format: 'jwk' });
|
|
92
|
-
resolve({ publicKey: pJwk, privateKey: prJwk });
|
|
93
|
-
} catch (exportErr) {
|
|
94
|
-
reject(exportErr);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async signProof(payload: Record<string, any>, keyPair: DPoPKeyPair): Promise<string> {
|
|
101
|
-
const crypto = getCrypto();
|
|
102
|
-
if (!crypto) {
|
|
103
|
-
throw new Error('Crypto module not available for signing');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 1. Construct Header
|
|
107
|
-
const header = {
|
|
108
|
-
typ: 'dpop+jwt',
|
|
109
|
-
alg: 'ES256',
|
|
110
|
-
jwk: keyPair.publicKey
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// 2. Add Claims (iat/jti)
|
|
114
|
-
const finalPayload = {
|
|
115
|
-
...payload,
|
|
116
|
-
iat: payload.iat || Math.floor(Date.now() / 1000),
|
|
117
|
-
jti: payload.jti || (crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`)
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// 3. Encode
|
|
121
|
-
const b64Header = this.base64Url(JSON.stringify(header));
|
|
122
|
-
const b64Payload = this.base64Url(JSON.stringify(finalPayload));
|
|
123
|
-
const signingInput = `${b64Header}.${b64Payload}`;
|
|
124
|
-
|
|
125
|
-
// 4. Sign - try WebCrypto first, fallback to Node.js crypto
|
|
126
|
-
if (crypto.subtle) {
|
|
127
|
-
try {
|
|
128
|
-
const privateKey = await crypto.subtle.importKey(
|
|
129
|
-
'jwk',
|
|
130
|
-
keyPair.privateKey,
|
|
131
|
-
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
132
|
-
false,
|
|
133
|
-
['sign']
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
const signatureBuffer = await crypto.subtle.sign(
|
|
137
|
-
{ name: 'ECDSA', hash: 'SHA-256' },
|
|
138
|
-
privateKey,
|
|
139
|
-
Buffer.from(signingInput)
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
// WebCrypto returns signature in IEEE P1363 format (r || s), which is what we need for JWT
|
|
143
|
-
return `${signingInput}.${this.base64UrlBuffer(Buffer.from(signatureBuffer))}`;
|
|
144
|
-
} catch (err) {
|
|
145
|
-
console.warn('[DPoP] WebCrypto sign failed, trying Node.js crypto', err);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Fallback: Node.js crypto API
|
|
150
|
-
const sign = crypto.createSign('SHA256');
|
|
151
|
-
sign.update(signingInput);
|
|
152
|
-
const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey as any, format: 'jwk' });
|
|
153
|
-
const signature = sign.sign(privateKeyObj);
|
|
154
|
-
|
|
155
|
-
return `${signingInput}.${this.base64UrlBuffer(signature)}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async hashAccessToken(accessToken: string): Promise<string> {
|
|
159
|
-
const crypto = getCrypto();
|
|
160
|
-
if (!crypto) {
|
|
161
|
-
throw new Error('Crypto module not available for hashing');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Try WebCrypto first
|
|
165
|
-
if (crypto.subtle) {
|
|
166
|
-
try {
|
|
167
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', Buffer.from(accessToken));
|
|
168
|
-
return this.base64UrlBuffer(Buffer.from(hashBuffer));
|
|
169
|
-
} catch (err) {
|
|
170
|
-
// Fallback to Node.js crypto
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const hash = crypto.createHash('sha256').update(accessToken).digest();
|
|
175
|
-
return this.base64UrlBuffer(hash);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// --- Helpers ---
|
|
179
|
-
private base64Url(str: string): string {
|
|
180
|
-
return this.base64UrlBuffer(Buffer.from(str));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private base64UrlBuffer(buf: Buffer | Uint8Array): string {
|
|
184
|
-
const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
185
|
-
return buffer.toString('base64')
|
|
186
|
-
.replace(/=/g, '')
|
|
187
|
-
.replace(/\+/g, '-')
|
|
188
|
-
.replace(/\//g, '_');
|
|
189
|
-
}
|
|
190
|
-
}
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { Buffer } from 'buffer';
|
|
3
|
+
import { DPoPCryptoProvider, DPoPKeyPair } from '@explorins/pers-sdk/core';
|
|
4
|
+
|
|
5
|
+
// Lazy-loaded crypto module - initialized on first use to avoid native module timing issues
|
|
6
|
+
let _crypto: any = null;
|
|
7
|
+
let _cryptoInitialized = false;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Lazily load the crypto module.
|
|
11
|
+
*
|
|
12
|
+
* Supports customer pre-initialization via global.__QuickCrypto for environments
|
|
13
|
+
* where native module timing is unpredictable (e.g., certain React Native setups).
|
|
14
|
+
*
|
|
15
|
+
* @returns The crypto module or null if unavailable
|
|
16
|
+
*/
|
|
17
|
+
function getCrypto(): any {
|
|
18
|
+
if (_cryptoInitialized) {
|
|
19
|
+
return _crypto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_cryptoInitialized = true;
|
|
23
|
+
|
|
24
|
+
if (Platform.OS === 'web') {
|
|
25
|
+
// On Web, we shouldn't be using this provider (Core SDK has WebDPoPProvider)
|
|
26
|
+
_crypto = {
|
|
27
|
+
generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); },
|
|
28
|
+
randomUUID: () => globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`
|
|
29
|
+
};
|
|
30
|
+
return _crypto;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check for customer pre-initialized global (for native module timing issues)
|
|
34
|
+
if (typeof global !== 'undefined' && (global as any).__QuickCrypto) {
|
|
35
|
+
_crypto = (global as any).__QuickCrypto;
|
|
36
|
+
return _crypto;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Standard lazy load
|
|
40
|
+
try {
|
|
41
|
+
_crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto.', e);
|
|
44
|
+
console.warn('Tip: If you encounter timing issues, pre-initialize in index.js: global.__QuickCrypto = require("react-native-quick-crypto")');
|
|
45
|
+
_crypto = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return _crypto;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
|
|
52
|
+
/**
|
|
53
|
+
* Generates a new key pair (ES256 recommended)
|
|
54
|
+
*
|
|
55
|
+
* Uses WebCrypto API (crypto.subtle) for cross-platform compatibility.
|
|
56
|
+
* Falls back to Node.js crypto API on iOS if needed.
|
|
57
|
+
*/
|
|
58
|
+
async generateKeyPair(options?: { extractable?: boolean }): Promise<DPoPKeyPair> {
|
|
59
|
+
const crypto = getCrypto();
|
|
60
|
+
if (!crypto) {
|
|
61
|
+
throw new Error('Crypto module not available. Install react-native-quick-crypto or pre-initialize global.__QuickCrypto');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Try WebCrypto API first (works on both iOS and Android)
|
|
65
|
+
if (crypto.subtle) {
|
|
66
|
+
try {
|
|
67
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
68
|
+
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
69
|
+
true, // extractable - required for JWK export
|
|
70
|
+
['sign', 'verify']
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const publicKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
|
74
|
+
const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
publicKey: publicKeyJwk,
|
|
78
|
+
privateKey: privateKeyJwk
|
|
79
|
+
};
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.warn('[DPoP] WebCrypto API failed, trying Node.js crypto API', err);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Fallback: Node.js crypto API (works on iOS)
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err: any, publicKey: any, privateKey: any) => {
|
|
88
|
+
if (err) return reject(err);
|
|
89
|
+
try {
|
|
90
|
+
const pJwk = publicKey.export({ format: 'jwk' });
|
|
91
|
+
const prJwk = privateKey.export({ format: 'jwk' });
|
|
92
|
+
resolve({ publicKey: pJwk, privateKey: prJwk });
|
|
93
|
+
} catch (exportErr) {
|
|
94
|
+
reject(exportErr);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async signProof(payload: Record<string, any>, keyPair: DPoPKeyPair): Promise<string> {
|
|
101
|
+
const crypto = getCrypto();
|
|
102
|
+
if (!crypto) {
|
|
103
|
+
throw new Error('Crypto module not available for signing');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 1. Construct Header
|
|
107
|
+
const header = {
|
|
108
|
+
typ: 'dpop+jwt',
|
|
109
|
+
alg: 'ES256',
|
|
110
|
+
jwk: keyPair.publicKey
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// 2. Add Claims (iat/jti)
|
|
114
|
+
const finalPayload = {
|
|
115
|
+
...payload,
|
|
116
|
+
iat: payload.iat || Math.floor(Date.now() / 1000),
|
|
117
|
+
jti: payload.jti || (crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`)
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// 3. Encode
|
|
121
|
+
const b64Header = this.base64Url(JSON.stringify(header));
|
|
122
|
+
const b64Payload = this.base64Url(JSON.stringify(finalPayload));
|
|
123
|
+
const signingInput = `${b64Header}.${b64Payload}`;
|
|
124
|
+
|
|
125
|
+
// 4. Sign - try WebCrypto first, fallback to Node.js crypto
|
|
126
|
+
if (crypto.subtle) {
|
|
127
|
+
try {
|
|
128
|
+
const privateKey = await crypto.subtle.importKey(
|
|
129
|
+
'jwk',
|
|
130
|
+
keyPair.privateKey,
|
|
131
|
+
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
132
|
+
false,
|
|
133
|
+
['sign']
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const signatureBuffer = await crypto.subtle.sign(
|
|
137
|
+
{ name: 'ECDSA', hash: 'SHA-256' },
|
|
138
|
+
privateKey,
|
|
139
|
+
Buffer.from(signingInput)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// WebCrypto returns signature in IEEE P1363 format (r || s), which is what we need for JWT
|
|
143
|
+
return `${signingInput}.${this.base64UrlBuffer(Buffer.from(signatureBuffer))}`;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.warn('[DPoP] WebCrypto sign failed, trying Node.js crypto', err);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Fallback: Node.js crypto API
|
|
150
|
+
const sign = crypto.createSign('SHA256');
|
|
151
|
+
sign.update(signingInput);
|
|
152
|
+
const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey as any, format: 'jwk' });
|
|
153
|
+
const signature = sign.sign(privateKeyObj);
|
|
154
|
+
|
|
155
|
+
return `${signingInput}.${this.base64UrlBuffer(signature)}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async hashAccessToken(accessToken: string): Promise<string> {
|
|
159
|
+
const crypto = getCrypto();
|
|
160
|
+
if (!crypto) {
|
|
161
|
+
throw new Error('Crypto module not available for hashing');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Try WebCrypto first
|
|
165
|
+
if (crypto.subtle) {
|
|
166
|
+
try {
|
|
167
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', Buffer.from(accessToken));
|
|
168
|
+
return this.base64UrlBuffer(Buffer.from(hashBuffer));
|
|
169
|
+
} catch (err) {
|
|
170
|
+
// Fallback to Node.js crypto
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const hash = crypto.createHash('sha256').update(accessToken).digest();
|
|
175
|
+
return this.base64UrlBuffer(hash);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// --- Helpers ---
|
|
179
|
+
private base64Url(str: string): string {
|
|
180
|
+
return this.base64UrlBuffer(Buffer.from(str));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private base64UrlBuffer(buf: Buffer | Uint8Array): string {
|
|
184
|
+
const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
185
|
+
return buffer.toString('base64')
|
|
186
|
+
.replace(/=/g, '')
|
|
187
|
+
.replace(/\+/g, '-')
|
|
188
|
+
.replace(/\//g, '_');
|
|
189
|
+
}
|
|
190
|
+
}
|