@bananalink-sdk/protocol 1.2.7
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 +604 -0
- package/dist/chunk-32OWUOZ3.js +308 -0
- package/dist/chunk-32OWUOZ3.js.map +1 -0
- package/dist/chunk-65HNHRJK.cjs +123 -0
- package/dist/chunk-65HNHRJK.cjs.map +1 -0
- package/dist/chunk-7KYDLL3B.js +480 -0
- package/dist/chunk-7KYDLL3B.js.map +1 -0
- package/dist/chunk-A6FLEJ7R.cjs +62 -0
- package/dist/chunk-A6FLEJ7R.cjs.map +1 -0
- package/dist/chunk-CUJK7ZTS.js +217 -0
- package/dist/chunk-CUJK7ZTS.js.map +1 -0
- package/dist/chunk-GI3BUPIH.cjs +236 -0
- package/dist/chunk-GI3BUPIH.cjs.map +1 -0
- package/dist/chunk-JXHV66Q4.js +106 -0
- package/dist/chunk-JXHV66Q4.js.map +1 -0
- package/dist/chunk-KNGZKGRS.cjs +552 -0
- package/dist/chunk-KNGZKGRS.cjs.map +1 -0
- package/dist/chunk-LELPCIE7.js +840 -0
- package/dist/chunk-LELPCIE7.js.map +1 -0
- package/dist/chunk-MCZG7QEM.cjs +310 -0
- package/dist/chunk-MCZG7QEM.cjs.map +1 -0
- package/dist/chunk-TCVKC227.js +56 -0
- package/dist/chunk-TCVKC227.js.map +1 -0
- package/dist/chunk-VXLUSU5B.cjs +856 -0
- package/dist/chunk-VXLUSU5B.cjs.map +1 -0
- package/dist/chunk-WCQVDF3K.js +12 -0
- package/dist/chunk-WCQVDF3K.js.map +1 -0
- package/dist/chunk-WGEGR3DF.cjs +15 -0
- package/dist/chunk-WGEGR3DF.cjs.map +1 -0
- package/dist/client-session-claim-3QF3noOr.d.ts +197 -0
- package/dist/client-session-claim-C4lUik3b.d.cts +197 -0
- package/dist/core-DMhuNfoz.d.cts +62 -0
- package/dist/core-DMhuNfoz.d.ts +62 -0
- package/dist/crypto/providers/noble-provider.cjs +14 -0
- package/dist/crypto/providers/noble-provider.cjs.map +1 -0
- package/dist/crypto/providers/noble-provider.d.cts +30 -0
- package/dist/crypto/providers/noble-provider.d.ts +30 -0
- package/dist/crypto/providers/noble-provider.js +5 -0
- package/dist/crypto/providers/noble-provider.js.map +1 -0
- package/dist/crypto/providers/node-provider.cjs +308 -0
- package/dist/crypto/providers/node-provider.cjs.map +1 -0
- package/dist/crypto/providers/node-provider.d.cts +32 -0
- package/dist/crypto/providers/node-provider.d.ts +32 -0
- package/dist/crypto/providers/node-provider.js +306 -0
- package/dist/crypto/providers/node-provider.js.map +1 -0
- package/dist/crypto/providers/quickcrypto-provider.cjs +339 -0
- package/dist/crypto/providers/quickcrypto-provider.cjs.map +1 -0
- package/dist/crypto/providers/quickcrypto-provider.d.cts +34 -0
- package/dist/crypto/providers/quickcrypto-provider.d.ts +34 -0
- package/dist/crypto/providers/quickcrypto-provider.js +337 -0
- package/dist/crypto/providers/quickcrypto-provider.js.map +1 -0
- package/dist/crypto/providers/webcrypto-provider.cjs +310 -0
- package/dist/crypto/providers/webcrypto-provider.cjs.map +1 -0
- package/dist/crypto/providers/webcrypto-provider.d.cts +30 -0
- package/dist/crypto/providers/webcrypto-provider.d.ts +30 -0
- package/dist/crypto/providers/webcrypto-provider.js +308 -0
- package/dist/crypto/providers/webcrypto-provider.js.map +1 -0
- package/dist/crypto-BUS06Qz-.d.cts +40 -0
- package/dist/crypto-BUS06Qz-.d.ts +40 -0
- package/dist/crypto-export.cjs +790 -0
- package/dist/crypto-export.cjs.map +1 -0
- package/dist/crypto-export.d.cts +257 -0
- package/dist/crypto-export.d.ts +257 -0
- package/dist/crypto-export.js +709 -0
- package/dist/crypto-export.js.map +1 -0
- package/dist/crypto-provider-deYoVIxi.d.cts +36 -0
- package/dist/crypto-provider-deYoVIxi.d.ts +36 -0
- package/dist/index.cjs +615 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +379 -0
- package/dist/index.d.ts +379 -0
- package/dist/index.js +504 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas-export.cjs +294 -0
- package/dist/schemas-export.cjs.map +1 -0
- package/dist/schemas-export.d.cts +1598 -0
- package/dist/schemas-export.d.ts +1598 -0
- package/dist/schemas-export.js +5 -0
- package/dist/schemas-export.js.map +1 -0
- package/dist/siwe-export.cjs +237 -0
- package/dist/siwe-export.cjs.map +1 -0
- package/dist/siwe-export.d.cts +27 -0
- package/dist/siwe-export.d.ts +27 -0
- package/dist/siwe-export.js +228 -0
- package/dist/siwe-export.js.map +1 -0
- package/dist/testing.cjs +54 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +20 -0
- package/dist/testing.d.ts +20 -0
- package/dist/testing.js +51 -0
- package/dist/testing.js.map +1 -0
- package/dist/validation-export.cjs +359 -0
- package/dist/validation-export.cjs.map +1 -0
- package/dist/validation-export.d.cts +3 -0
- package/dist/validation-export.d.ts +3 -0
- package/dist/validation-export.js +6 -0
- package/dist/validation-export.js.map +1 -0
- package/dist/validators-export.cjs +73 -0
- package/dist/validators-export.cjs.map +1 -0
- package/dist/validators-export.d.cts +37 -0
- package/dist/validators-export.d.ts +37 -0
- package/dist/validators-export.js +4 -0
- package/dist/validators-export.js.map +1 -0
- package/package.json +140 -0
- package/src/constants/index.ts +205 -0
- package/src/crypto/context.ts +228 -0
- package/src/crypto/diagnostics.ts +772 -0
- package/src/crypto/errors.ts +114 -0
- package/src/crypto/index.ts +89 -0
- package/src/crypto/payload-handler.ts +102 -0
- package/src/crypto/providers/compliance-provider.ts +579 -0
- package/src/crypto/providers/factory.ts +204 -0
- package/src/crypto/providers/index.ts +44 -0
- package/src/crypto/providers/noble-provider.ts +392 -0
- package/src/crypto/providers/node-provider.ts +433 -0
- package/src/crypto/providers/quickcrypto-provider.ts +483 -0
- package/src/crypto/providers/registry.ts +129 -0
- package/src/crypto/providers/webcrypto-provider.ts +364 -0
- package/src/crypto/session-security.ts +185 -0
- package/src/crypto/types.ts +93 -0
- package/src/crypto/utils.ts +190 -0
- package/src/crypto-export.ts +21 -0
- package/src/index.ts +38 -0
- package/src/schemas/auth.ts +60 -0
- package/src/schemas/client-messages.ts +57 -0
- package/src/schemas/core.ts +144 -0
- package/src/schemas/crypto.ts +65 -0
- package/src/schemas/discovery.ts +79 -0
- package/src/schemas/index.ts +239 -0
- package/src/schemas/relay-messages.ts +45 -0
- package/src/schemas/wallet-messages.ts +177 -0
- package/src/schemas-export.ts +23 -0
- package/src/siwe-export.ts +27 -0
- package/src/testing.ts +71 -0
- package/src/types/auth.ts +60 -0
- package/src/types/client-messages.ts +84 -0
- package/src/types/core.ts +131 -0
- package/src/types/crypto-provider.ts +264 -0
- package/src/types/crypto.ts +90 -0
- package/src/types/discovery.ts +50 -0
- package/src/types/errors.ts +87 -0
- package/src/types/index.ts +197 -0
- package/src/types/post-auth-operations.ts +363 -0
- package/src/types/providers.ts +72 -0
- package/src/types/relay-messages.ts +60 -0
- package/src/types/request-lifecycle.ts +161 -0
- package/src/types/signing-operations.ts +99 -0
- package/src/types/wallet-messages.ts +251 -0
- package/src/utils/client-session-claim.ts +188 -0
- package/src/utils/index.ts +54 -0
- package/src/utils/public-keys.ts +49 -0
- package/src/utils/siwe.ts +362 -0
- package/src/utils/url-decoding.ts +126 -0
- package/src/utils/url-encoding.ts +144 -0
- package/src/utils/wallet-session-claim.ts +188 -0
- package/src/validation-export.ts +32 -0
- package/src/validators/index.ts +222 -0
- package/src/validators-export.ts +8 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto Provider Diagnostics
|
|
3
|
+
*
|
|
4
|
+
* Utilities for testing, benchmarking, and diagnosing crypto provider issues.
|
|
5
|
+
* These tools help developers understand provider availability and performance.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Test if a provider actually works
|
|
10
|
+
* const result = await testProvider('webcrypto');
|
|
11
|
+
* if (result.success) {
|
|
12
|
+
* console.log('WebCrypto is working!');
|
|
13
|
+
* } else {
|
|
14
|
+
* console.error('WebCrypto failed:', result.error);
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* // Get detailed environment diagnostics
|
|
18
|
+
* const diag = await diagnoseEnvironment();
|
|
19
|
+
* console.log(diag.toMarkdown());
|
|
20
|
+
*
|
|
21
|
+
* // Benchmark providers
|
|
22
|
+
* const bench = await benchmarkProvider('webcrypto');
|
|
23
|
+
* console.log(`ECDH: ${bench.ecdh.avgMs}ms`);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { Logger } from '@bananalink-sdk/logger';
|
|
28
|
+
import type {
|
|
29
|
+
CryptoProvider,
|
|
30
|
+
CryptoProviderType,
|
|
31
|
+
PlatformDetectionResult,
|
|
32
|
+
} from '../types/crypto-provider';
|
|
33
|
+
import { getRegisteredCryptoProviders } from './providers';
|
|
34
|
+
import { detectPlatform } from './utils';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Result of testing a specific crypto operation
|
|
38
|
+
*/
|
|
39
|
+
export interface OperationTestResult {
|
|
40
|
+
/** Operation name */
|
|
41
|
+
operation: string;
|
|
42
|
+
/** Whether the operation succeeded */
|
|
43
|
+
success: boolean;
|
|
44
|
+
/** Execution time in milliseconds */
|
|
45
|
+
durationMs?: number;
|
|
46
|
+
/** Error message if failed */
|
|
47
|
+
error?: string;
|
|
48
|
+
/** Additional details */
|
|
49
|
+
details?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Result of testing a crypto provider
|
|
54
|
+
*/
|
|
55
|
+
export interface ProviderTestResult {
|
|
56
|
+
/** Provider type tested */
|
|
57
|
+
provider: CryptoProviderType;
|
|
58
|
+
/** Whether the provider is available */
|
|
59
|
+
available: boolean;
|
|
60
|
+
/** Overall success (all operations passed) */
|
|
61
|
+
success: boolean;
|
|
62
|
+
/** Individual operation test results */
|
|
63
|
+
operations: OperationTestResult[];
|
|
64
|
+
/** Total test duration in milliseconds */
|
|
65
|
+
totalDurationMs: number;
|
|
66
|
+
/** Error message if provider not available */
|
|
67
|
+
unavailableReason?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Performance benchmark result for a provider
|
|
72
|
+
*/
|
|
73
|
+
export interface ProviderBenchmark {
|
|
74
|
+
/** Provider type tested */
|
|
75
|
+
provider: CryptoProviderType;
|
|
76
|
+
/** ECDH key generation benchmark */
|
|
77
|
+
ecdh: {
|
|
78
|
+
/** Average time in milliseconds */
|
|
79
|
+
avgMs: number;
|
|
80
|
+
/** Minimum time in milliseconds */
|
|
81
|
+
minMs: number;
|
|
82
|
+
/** Maximum time in milliseconds */
|
|
83
|
+
maxMs: number;
|
|
84
|
+
/** Number of iterations */
|
|
85
|
+
iterations: number;
|
|
86
|
+
};
|
|
87
|
+
/** Random bytes generation benchmark */
|
|
88
|
+
randomBytes: {
|
|
89
|
+
/** Average time in milliseconds */
|
|
90
|
+
avgMs: number;
|
|
91
|
+
/** Minimum time in milliseconds */
|
|
92
|
+
minMs: number;
|
|
93
|
+
/** Maximum time in milliseconds */
|
|
94
|
+
maxMs: number;
|
|
95
|
+
/** Number of iterations */
|
|
96
|
+
iterations: number;
|
|
97
|
+
};
|
|
98
|
+
/** AES-GCM encryption benchmark */
|
|
99
|
+
aesGcm: {
|
|
100
|
+
/** Average time in milliseconds */
|
|
101
|
+
avgMs: number;
|
|
102
|
+
/** Minimum time in milliseconds */
|
|
103
|
+
minMs: number;
|
|
104
|
+
/** Maximum time in milliseconds */
|
|
105
|
+
maxMs: number;
|
|
106
|
+
/** Number of iterations */
|
|
107
|
+
iterations: number;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Provider detection summary
|
|
113
|
+
*/
|
|
114
|
+
export interface ProviderDetection {
|
|
115
|
+
/** WebCrypto provider available */
|
|
116
|
+
webCrypto: boolean;
|
|
117
|
+
/** Node.js crypto provider available */
|
|
118
|
+
node: boolean;
|
|
119
|
+
/** Noble provider available (pure JS fallback) */
|
|
120
|
+
noble: boolean;
|
|
121
|
+
/** QuickCrypto provider available (React Native) */
|
|
122
|
+
quickCrypto: boolean;
|
|
123
|
+
/** Recommended provider for this environment */
|
|
124
|
+
recommended?: CryptoProviderType;
|
|
125
|
+
/** Detailed capability information */
|
|
126
|
+
capabilities: Record<CryptoProviderType, {
|
|
127
|
+
available: boolean;
|
|
128
|
+
tested: boolean;
|
|
129
|
+
success: boolean;
|
|
130
|
+
}>;
|
|
131
|
+
/** Provider-specific recommendations */
|
|
132
|
+
recommendations: string[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Comprehensive environment diagnostics
|
|
137
|
+
*/
|
|
138
|
+
export interface EnvironmentDiagnostics {
|
|
139
|
+
/** Timestamp of diagnostics */
|
|
140
|
+
timestamp: string;
|
|
141
|
+
/** Platform detection information */
|
|
142
|
+
platform: PlatformDetectionResult;
|
|
143
|
+
/** Provider detection summary */
|
|
144
|
+
detection: ProviderDetection;
|
|
145
|
+
/** List of registered providers */
|
|
146
|
+
registeredProviders: CryptoProviderType[];
|
|
147
|
+
/** Provider test results */
|
|
148
|
+
providerTests: ProviderTestResult[];
|
|
149
|
+
/** Recommended provider (first successful test) */
|
|
150
|
+
recommendedProvider?: CryptoProviderType;
|
|
151
|
+
/** Warnings and recommendations */
|
|
152
|
+
warnings: string[];
|
|
153
|
+
/** Platform-specific recommendations */
|
|
154
|
+
recommendations: string[];
|
|
155
|
+
/** Convert to human-readable markdown */
|
|
156
|
+
toMarkdown(): string;
|
|
157
|
+
/** Convert to JSON string */
|
|
158
|
+
toJSON(): string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Test a specific crypto provider with real operations
|
|
163
|
+
*
|
|
164
|
+
* @param providerType - The provider type to test
|
|
165
|
+
* @param logger - Optional logger for debug output
|
|
166
|
+
* @returns Test result with success status and operation details
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* const result = await testProvider('webcrypto');
|
|
171
|
+
* if (!result.success) {
|
|
172
|
+
* console.error('Provider test failed:', result);
|
|
173
|
+
* result.operations.forEach(op => {
|
|
174
|
+
* if (!op.success) {
|
|
175
|
+
* console.error(` ${op.operation}: ${op.error}`);
|
|
176
|
+
* }
|
|
177
|
+
* });
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export async function testProvider(
|
|
182
|
+
providerType: CryptoProviderType,
|
|
183
|
+
logger?: Logger
|
|
184
|
+
): Promise<ProviderTestResult> {
|
|
185
|
+
const startTime = performance.now();
|
|
186
|
+
const operations: OperationTestResult[] = [];
|
|
187
|
+
|
|
188
|
+
let provider: CryptoProvider;
|
|
189
|
+
try {
|
|
190
|
+
// Create the specific provider without fallback to test it directly
|
|
191
|
+
switch (providerType) {
|
|
192
|
+
case 'webcrypto':
|
|
193
|
+
provider = new (await import('./providers/webcrypto-provider')).WebCryptoProvider();
|
|
194
|
+
break;
|
|
195
|
+
case 'noble':
|
|
196
|
+
provider = new (await import('./providers/noble-provider')).NobleCryptoProvider(logger);
|
|
197
|
+
break;
|
|
198
|
+
case 'node':
|
|
199
|
+
provider = new (await import('./providers/node-provider')).NodeCryptoProvider(logger);
|
|
200
|
+
break;
|
|
201
|
+
case 'quickcrypto':
|
|
202
|
+
provider = new (await import('./providers/quickcrypto-provider')).QuickCryptoProvider(logger);
|
|
203
|
+
break;
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`Unknown provider type: ${String(providerType)}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!provider.isAvailable) {
|
|
209
|
+
return {
|
|
210
|
+
provider: providerType,
|
|
211
|
+
available: false,
|
|
212
|
+
success: false,
|
|
213
|
+
operations: [],
|
|
214
|
+
totalDurationMs: performance.now() - startTime,
|
|
215
|
+
unavailableReason: `Provider ${providerType} not available in this environment`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return {
|
|
220
|
+
provider: providerType,
|
|
221
|
+
available: false,
|
|
222
|
+
success: false,
|
|
223
|
+
operations: [],
|
|
224
|
+
totalDurationMs: performance.now() - startTime,
|
|
225
|
+
unavailableReason: error instanceof Error ? error.message : String(error),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Test 1: Random bytes generation
|
|
230
|
+
try {
|
|
231
|
+
const opStart = performance.now();
|
|
232
|
+
const bytes = provider.randomBytes(32);
|
|
233
|
+
const duration = performance.now() - opStart;
|
|
234
|
+
|
|
235
|
+
operations.push({
|
|
236
|
+
operation: 'randomBytes',
|
|
237
|
+
success: true,
|
|
238
|
+
durationMs: duration,
|
|
239
|
+
details: {
|
|
240
|
+
bytesLength: bytes.byteLength,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
} catch (error) {
|
|
244
|
+
operations.push({
|
|
245
|
+
operation: 'randomBytes',
|
|
246
|
+
success: false,
|
|
247
|
+
error: error instanceof Error ? error.message : String(error),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Test 2: ECDH key pair generation
|
|
252
|
+
try {
|
|
253
|
+
const opStart = performance.now();
|
|
254
|
+
const keyPair = await provider.generateKeyPair();
|
|
255
|
+
const duration = performance.now() - opStart;
|
|
256
|
+
|
|
257
|
+
operations.push({
|
|
258
|
+
operation: 'generateKeyPair',
|
|
259
|
+
success: true,
|
|
260
|
+
durationMs: duration,
|
|
261
|
+
details: {
|
|
262
|
+
publicKeyType: keyPair.publicKey.type,
|
|
263
|
+
privateKeyType: keyPair.privateKey.type,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
} catch (error) {
|
|
267
|
+
operations.push({
|
|
268
|
+
operation: 'generateKeyPair',
|
|
269
|
+
success: false,
|
|
270
|
+
error: error instanceof Error ? error.message : String(error),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Test 3: Key export/import
|
|
275
|
+
try {
|
|
276
|
+
const opStart = performance.now();
|
|
277
|
+
const keyPair = await provider.generateKeyPair();
|
|
278
|
+
const publicKeyData = await provider.exportPublicKey(keyPair.publicKey);
|
|
279
|
+
const importedPublicKey = await provider.importPublicKey(publicKeyData);
|
|
280
|
+
const duration = performance.now() - opStart;
|
|
281
|
+
|
|
282
|
+
operations.push({
|
|
283
|
+
operation: 'exportImportKey',
|
|
284
|
+
success: true,
|
|
285
|
+
durationMs: duration,
|
|
286
|
+
details: {
|
|
287
|
+
exportedKeySize: publicKeyData.byteLength,
|
|
288
|
+
importedKeyType: importedPublicKey.type,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
} catch (error) {
|
|
292
|
+
operations.push({
|
|
293
|
+
operation: 'exportImportKey',
|
|
294
|
+
success: false,
|
|
295
|
+
error: error instanceof Error ? error.message : String(error),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Test 4: ECDH key agreement
|
|
300
|
+
try {
|
|
301
|
+
const opStart = performance.now();
|
|
302
|
+
const keyPair1 = await provider.generateKeyPair();
|
|
303
|
+
const keyPair2 = await provider.generateKeyPair();
|
|
304
|
+
// Derive shared secrets from both sides (just verify they can be derived)
|
|
305
|
+
await provider.deriveSharedSecret(keyPair1.privateKey, keyPair2.publicKey);
|
|
306
|
+
await provider.deriveSharedSecret(keyPair2.privateKey, keyPair1.publicKey);
|
|
307
|
+
const duration = performance.now() - opStart;
|
|
308
|
+
|
|
309
|
+
// Both shared secrets were derived successfully
|
|
310
|
+
operations.push({
|
|
311
|
+
operation: 'deriveSharedSecret',
|
|
312
|
+
success: true,
|
|
313
|
+
durationMs: duration,
|
|
314
|
+
details: {
|
|
315
|
+
sharedSecretsGenerated: 2,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
} catch (error) {
|
|
319
|
+
operations.push({
|
|
320
|
+
operation: 'deriveSharedSecret',
|
|
321
|
+
success: false,
|
|
322
|
+
error: error instanceof Error ? error.message : String(error),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Test 5: AES-GCM encryption/decryption
|
|
327
|
+
try {
|
|
328
|
+
const opStart = performance.now();
|
|
329
|
+
const keyPair = await provider.generateKeyPair();
|
|
330
|
+
const sharedSecret = await provider.deriveSharedSecret(keyPair.privateKey, keyPair.publicKey);
|
|
331
|
+
const salt = provider.randomBytes(32);
|
|
332
|
+
const info = provider.randomBytes(32);
|
|
333
|
+
const encryptionKey = await provider.deriveEncryptionKey(sharedSecret, salt, info);
|
|
334
|
+
|
|
335
|
+
const plaintext = new TextEncoder().encode('Hello, World!');
|
|
336
|
+
const iv = provider.randomBytes(12);
|
|
337
|
+
const ciphertext = await provider.encrypt(encryptionKey, plaintext, iv);
|
|
338
|
+
const decrypted = await provider.decrypt(encryptionKey, ciphertext, iv);
|
|
339
|
+
const decryptedText = new TextDecoder().decode(decrypted);
|
|
340
|
+
const duration = performance.now() - opStart;
|
|
341
|
+
|
|
342
|
+
operations.push({
|
|
343
|
+
operation: 'aesGcmEncryptDecrypt',
|
|
344
|
+
success: decryptedText === 'Hello, World!',
|
|
345
|
+
durationMs: duration,
|
|
346
|
+
details: {
|
|
347
|
+
plaintextSize: plaintext.byteLength,
|
|
348
|
+
ciphertextSize: ciphertext.byteLength,
|
|
349
|
+
decryptedCorrectly: decryptedText === 'Hello, World!',
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
operations.push({
|
|
354
|
+
operation: 'aesGcmEncryptDecrypt',
|
|
355
|
+
success: false,
|
|
356
|
+
error: error instanceof Error ? error.message : String(error),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const totalDuration = performance.now() - startTime;
|
|
361
|
+
const allSuccess = operations.every(op => op.success);
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
provider: providerType,
|
|
365
|
+
available: true,
|
|
366
|
+
success: allSuccess,
|
|
367
|
+
operations,
|
|
368
|
+
totalDurationMs: totalDuration,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Benchmark a crypto provider's performance
|
|
374
|
+
*
|
|
375
|
+
* @param providerType - The provider type to benchmark
|
|
376
|
+
* @param iterations - Number of iterations for each operation (default: 10)
|
|
377
|
+
* @param logger - Optional logger for debug output
|
|
378
|
+
* @returns Benchmark results with timing statistics
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* const bench = await benchmarkProvider('noble', 20);
|
|
383
|
+
* console.log(`ECDH: ${bench.ecdh.avgMs}ms (min: ${bench.ecdh.minMs}ms, max: ${bench.ecdh.maxMs}ms)`);
|
|
384
|
+
* console.log(`Random: ${bench.randomBytes.avgMs}ms`);
|
|
385
|
+
* console.log(`AES-GCM: ${bench.aesGcm.avgMs}ms`);
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
export async function benchmarkProvider(
|
|
389
|
+
providerType: CryptoProviderType,
|
|
390
|
+
iterations: number = 10,
|
|
391
|
+
logger?: Logger
|
|
392
|
+
): Promise<ProviderBenchmark> {
|
|
393
|
+
// Create the specific provider without fallback to test it directly
|
|
394
|
+
let provider: CryptoProvider;
|
|
395
|
+
try {
|
|
396
|
+
switch (providerType) {
|
|
397
|
+
case 'webcrypto':
|
|
398
|
+
provider = new (await import('./providers/webcrypto-provider')).WebCryptoProvider();
|
|
399
|
+
break;
|
|
400
|
+
case 'noble':
|
|
401
|
+
provider = new (await import('./providers/noble-provider')).NobleCryptoProvider(logger);
|
|
402
|
+
break;
|
|
403
|
+
case 'node':
|
|
404
|
+
provider = new (await import('./providers/node-provider')).NodeCryptoProvider(logger);
|
|
405
|
+
break;
|
|
406
|
+
case 'quickcrypto':
|
|
407
|
+
provider = new (await import('./providers/quickcrypto-provider')).QuickCryptoProvider(logger);
|
|
408
|
+
break;
|
|
409
|
+
default:
|
|
410
|
+
throw new Error(`Unknown provider type: ${String(providerType)}`);
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
throw new Error(
|
|
414
|
+
`Provider ${providerType} failed to initialize: ${error instanceof Error ? error.message : String(error)}`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!provider.isAvailable) {
|
|
419
|
+
throw new Error(`Provider ${providerType} not available for benchmarking`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Benchmark ECDH key generation
|
|
423
|
+
const ecdhTimes: number[] = [];
|
|
424
|
+
for (let i = 0; i < iterations; i++) {
|
|
425
|
+
const start = performance.now();
|
|
426
|
+
await provider.generateKeyPair();
|
|
427
|
+
ecdhTimes.push(performance.now() - start);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Benchmark random bytes
|
|
431
|
+
const randomBytesTimes: number[] = [];
|
|
432
|
+
for (let i = 0; i < iterations; i++) {
|
|
433
|
+
const start = performance.now();
|
|
434
|
+
provider.randomBytes(32);
|
|
435
|
+
randomBytesTimes.push(performance.now() - start);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Benchmark AES-GCM
|
|
439
|
+
const aesGcmTimes: number[] = [];
|
|
440
|
+
// Prepare keys once
|
|
441
|
+
const keyPair = await provider.generateKeyPair();
|
|
442
|
+
const sharedSecret = await provider.deriveSharedSecret(keyPair.privateKey, keyPair.publicKey);
|
|
443
|
+
const salt = provider.randomBytes(32);
|
|
444
|
+
const info = provider.randomBytes(32);
|
|
445
|
+
const encryptionKey = await provider.deriveEncryptionKey(sharedSecret, salt, info);
|
|
446
|
+
const plaintext = provider.randomBytes(1024); // 1KB test data
|
|
447
|
+
const iv = provider.randomBytes(12);
|
|
448
|
+
|
|
449
|
+
for (let i = 0; i < iterations; i++) {
|
|
450
|
+
const start = performance.now();
|
|
451
|
+
await provider.encrypt(encryptionKey, plaintext, iv);
|
|
452
|
+
aesGcmTimes.push(performance.now() - start);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
provider: providerType,
|
|
457
|
+
ecdh: {
|
|
458
|
+
avgMs: ecdhTimes.reduce((a, b) => a + b, 0) / iterations,
|
|
459
|
+
minMs: Math.min(...ecdhTimes),
|
|
460
|
+
maxMs: Math.max(...ecdhTimes),
|
|
461
|
+
iterations,
|
|
462
|
+
},
|
|
463
|
+
randomBytes: {
|
|
464
|
+
avgMs: randomBytesTimes.reduce((a, b) => a + b, 0) / iterations,
|
|
465
|
+
minMs: Math.min(...randomBytesTimes),
|
|
466
|
+
maxMs: Math.max(...randomBytesTimes),
|
|
467
|
+
iterations,
|
|
468
|
+
},
|
|
469
|
+
aesGcm: {
|
|
470
|
+
avgMs: aesGcmTimes.reduce((a, b) => a + b, 0) / iterations,
|
|
471
|
+
minMs: Math.min(...aesGcmTimes),
|
|
472
|
+
maxMs: Math.max(...aesGcmTimes),
|
|
473
|
+
iterations,
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Get comprehensive environment diagnostics
|
|
480
|
+
*
|
|
481
|
+
* @param logger - Optional logger for debug output
|
|
482
|
+
* @returns Complete diagnostics including registered providers, test results, and recommendations
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* const diag = await diagnoseEnvironment();
|
|
487
|
+
*
|
|
488
|
+
* // Print markdown report
|
|
489
|
+
* console.log(diag.toMarkdown());
|
|
490
|
+
*
|
|
491
|
+
* // Or access data programmatically
|
|
492
|
+
* console.log('Registered:', diag.registeredProviders);
|
|
493
|
+
* console.log('Recommended:', diag.recommendedProvider);
|
|
494
|
+
* diag.providerTests.forEach(test => {
|
|
495
|
+
* console.log(`${test.provider}: ${test.success ? '✅' : '❌'}`);
|
|
496
|
+
* });
|
|
497
|
+
* ```
|
|
498
|
+
*/
|
|
499
|
+
export async function diagnoseEnvironment(logger?: Logger): Promise<EnvironmentDiagnostics> {
|
|
500
|
+
const platform = detectPlatform();
|
|
501
|
+
const registeredProviders = getRegisteredCryptoProviders();
|
|
502
|
+
const warnings: string[] = [];
|
|
503
|
+
const recommendations: string[] = [];
|
|
504
|
+
|
|
505
|
+
// Test all registered providers
|
|
506
|
+
const providerTests = await Promise.all(
|
|
507
|
+
registeredProviders.map(type => testProvider(type, logger))
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
// Find recommended provider (first successful test)
|
|
511
|
+
const recommendedProvider = providerTests.find(test => test.success && test.available)?.provider;
|
|
512
|
+
|
|
513
|
+
// Build provider detection summary
|
|
514
|
+
const capabilities: Record<CryptoProviderType, { available: boolean; tested: boolean; success: boolean }> = {
|
|
515
|
+
webcrypto: { available: false, tested: false, success: false },
|
|
516
|
+
node: { available: false, tested: false, success: false },
|
|
517
|
+
noble: { available: false, tested: false, success: false },
|
|
518
|
+
quickcrypto: { available: false, tested: false, success: false },
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
providerTests.forEach(test => {
|
|
522
|
+
capabilities[test.provider] = {
|
|
523
|
+
available: test.available,
|
|
524
|
+
tested: true,
|
|
525
|
+
success: test.success,
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const detectionRecommendations: string[] = [];
|
|
530
|
+
if (!capabilities.noble.success) {
|
|
531
|
+
detectionRecommendations.push("Noble provider should always work as a fallback");
|
|
532
|
+
}
|
|
533
|
+
if (platform.isNode && !capabilities.node.tested) {
|
|
534
|
+
detectionRecommendations.push("Consider testing 'node' provider for optimal Node.js performance");
|
|
535
|
+
}
|
|
536
|
+
if (platform.isBrowser && !capabilities.webcrypto.tested) {
|
|
537
|
+
detectionRecommendations.push("Consider testing 'webcrypto' provider for browser environments");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const detection: ProviderDetection = {
|
|
541
|
+
webCrypto: capabilities.webcrypto.available && capabilities.webcrypto.success,
|
|
542
|
+
node: capabilities.node.available && capabilities.node.success,
|
|
543
|
+
noble: capabilities.noble.available && capabilities.noble.success,
|
|
544
|
+
quickCrypto: capabilities.quickcrypto.available && capabilities.quickcrypto.success,
|
|
545
|
+
recommended: recommendedProvider,
|
|
546
|
+
capabilities,
|
|
547
|
+
recommendations: detectionRecommendations,
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// Generate warnings
|
|
551
|
+
providerTests.forEach(test => {
|
|
552
|
+
if (!test.success && test.available) {
|
|
553
|
+
warnings.push(`${test.provider} provider registered but failed operation tests`);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
if (registeredProviders.length === 0) {
|
|
558
|
+
warnings.push('No crypto providers registered. Import at least one provider.');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (!recommendedProvider) {
|
|
562
|
+
warnings.push('No working crypto provider found. All registered providers failed tests.');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Generate platform-specific recommendations
|
|
566
|
+
if (platform.isNode) {
|
|
567
|
+
if (!registeredProviders.includes('node')) {
|
|
568
|
+
recommendations.push("Consider importing 'node' provider for optimal Node.js performance");
|
|
569
|
+
}
|
|
570
|
+
if (!recommendedProvider) {
|
|
571
|
+
recommendations.push("Import '@bananalink-sdk/protocol/crypto/provider/node' for Node.js");
|
|
572
|
+
}
|
|
573
|
+
} else if (platform.isBrowser) {
|
|
574
|
+
if (!registeredProviders.includes('webcrypto')) {
|
|
575
|
+
recommendations.push("Consider importing 'webcrypto' provider for browsers");
|
|
576
|
+
}
|
|
577
|
+
if (!recommendedProvider) {
|
|
578
|
+
recommendations.push("Import '@bananalink-sdk/protocol/crypto/provider/webcrypto' for browsers");
|
|
579
|
+
}
|
|
580
|
+
} else if (platform.isReactNative) {
|
|
581
|
+
if (!registeredProviders.includes('quickcrypto')) {
|
|
582
|
+
recommendations.push("Consider importing 'quickcrypto' provider for React Native");
|
|
583
|
+
}
|
|
584
|
+
if (!recommendedProvider) {
|
|
585
|
+
recommendations.push("Import '@bananalink-sdk/protocol/crypto/provider/quickcrypto' for React Native");
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Universal recommendation
|
|
590
|
+
if (!recommendedProvider || registeredProviders.length === 0) {
|
|
591
|
+
recommendations.push("Import '@bananalink-sdk/protocol/crypto/provider/noble' as a universal fallback");
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const diagnostics: EnvironmentDiagnostics = {
|
|
595
|
+
timestamp: new Date().toISOString(),
|
|
596
|
+
platform,
|
|
597
|
+
detection,
|
|
598
|
+
registeredProviders,
|
|
599
|
+
providerTests,
|
|
600
|
+
recommendedProvider,
|
|
601
|
+
warnings,
|
|
602
|
+
recommendations,
|
|
603
|
+
|
|
604
|
+
toMarkdown(): string {
|
|
605
|
+
let md = '# Crypto Environment Diagnostics\n\n';
|
|
606
|
+
md += `**Generated:** ${this.timestamp}\n\n`;
|
|
607
|
+
|
|
608
|
+
// Platform section
|
|
609
|
+
md += '## Platform\n\n';
|
|
610
|
+
const platformType = this.platform.isNode
|
|
611
|
+
? 'Node.js'
|
|
612
|
+
: this.platform.isBrowser
|
|
613
|
+
? 'Browser'
|
|
614
|
+
: this.platform.isReactNative
|
|
615
|
+
? 'React Native'
|
|
616
|
+
: 'Unknown';
|
|
617
|
+
md += `- **Type:** ${platformType}\n`;
|
|
618
|
+
if (this.platform.platform) {
|
|
619
|
+
md += `- **OS:** ${this.platform.platform}\n`;
|
|
620
|
+
}
|
|
621
|
+
if (this.platform.userAgent) {
|
|
622
|
+
// Truncate long user agents
|
|
623
|
+
const ua = this.platform.userAgent.length > 80
|
|
624
|
+
? `${this.platform.userAgent.substring(0, 80)}...`
|
|
625
|
+
: this.platform.userAgent;
|
|
626
|
+
md += `- **User Agent:** ${ua}\n`;
|
|
627
|
+
}
|
|
628
|
+
md += '\n';
|
|
629
|
+
|
|
630
|
+
// Provider Detection section
|
|
631
|
+
md += '## Provider Detection\n\n';
|
|
632
|
+
md += `- **WebCrypto:** ${this.detection.webCrypto ? '✅' : '❌'}\n`;
|
|
633
|
+
md += `- **Node.js Crypto:** ${this.detection.node ? '✅' : '❌'}\n`;
|
|
634
|
+
md += `- **Noble (Pure JS):** ${this.detection.noble ? '✅' : '❌'}\n`;
|
|
635
|
+
md += `- **QuickCrypto (RN):** ${this.detection.quickCrypto ? '✅' : '❌'}\n`;
|
|
636
|
+
if (this.detection.recommended) {
|
|
637
|
+
md += `- **Recommended:** ${this.detection.recommended}\n`;
|
|
638
|
+
}
|
|
639
|
+
md += '\n';
|
|
640
|
+
|
|
641
|
+
// Capabilities section
|
|
642
|
+
md += '## Capabilities\n\n';
|
|
643
|
+
Object.entries(this.detection.capabilities).forEach(([provider, capability]) => {
|
|
644
|
+
const status = capability.tested
|
|
645
|
+
? capability.success ? '✅ Available' : '❌ Failed'
|
|
646
|
+
: '⚪ Not Tested';
|
|
647
|
+
md += `- **${provider}:** ${status}\n`;
|
|
648
|
+
});
|
|
649
|
+
md += '\n';
|
|
650
|
+
|
|
651
|
+
// Registered providers section
|
|
652
|
+
md += '## Registered Providers\n\n';
|
|
653
|
+
if (this.registeredProviders.length === 0) {
|
|
654
|
+
md += 'No providers registered.\n\n';
|
|
655
|
+
} else {
|
|
656
|
+
this.registeredProviders.forEach(provider => {
|
|
657
|
+
md += `- ${provider}${provider === this.recommendedProvider ? ' (recommended)' : ''}\n`;
|
|
658
|
+
});
|
|
659
|
+
md += '\n';
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Provider tests section
|
|
663
|
+
md += '## Provider Tests\n\n';
|
|
664
|
+
if (this.providerTests.length === 0) {
|
|
665
|
+
md += 'No tests run (no providers registered).\n\n';
|
|
666
|
+
} else {
|
|
667
|
+
this.providerTests.forEach(test => {
|
|
668
|
+
md += `### ${test.provider}\n\n`;
|
|
669
|
+
md += `- **Available:** ${test.available ? '✅' : '❌'}\n`;
|
|
670
|
+
if (!test.available) {
|
|
671
|
+
md += `- **Reason:** ${test.unavailableReason}\n`;
|
|
672
|
+
} else {
|
|
673
|
+
md += `- **Overall Success:** ${test.success ? '✅' : '❌'}\n`;
|
|
674
|
+
md += `- **Total Duration:** ${test.totalDurationMs.toFixed(2)}ms\n\n`;
|
|
675
|
+
md += '**Operations:**\n\n';
|
|
676
|
+
test.operations.forEach(op => {
|
|
677
|
+
md += `- **${op.operation}:** ${op.success ? '✅' : '❌'}`;
|
|
678
|
+
if (op.durationMs) md += ` (${op.durationMs.toFixed(2)}ms)`;
|
|
679
|
+
if (op.error) md += ` - ${op.error}`;
|
|
680
|
+
md += '\n';
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
md += '\n';
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Recommendations section
|
|
688
|
+
md += '## Recommendations\n\n';
|
|
689
|
+
if (this.recommendedProvider) {
|
|
690
|
+
md += `- Recommended: Use '${this.recommendedProvider}' provider (passed all tests)\n`;
|
|
691
|
+
}
|
|
692
|
+
if (this.recommendations.length > 0) {
|
|
693
|
+
this.recommendations.forEach(rec => {
|
|
694
|
+
md += `- ${rec}\n`;
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
md += '\n';
|
|
698
|
+
|
|
699
|
+
// Warnings section
|
|
700
|
+
if (this.warnings.length > 0) {
|
|
701
|
+
md += '## Warnings\n\n';
|
|
702
|
+
this.warnings.forEach(warning => {
|
|
703
|
+
md += `⚠️ ${warning}\n\n`;
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return md;
|
|
708
|
+
},
|
|
709
|
+
|
|
710
|
+
toJSON(): string {
|
|
711
|
+
return JSON.stringify(
|
|
712
|
+
{
|
|
713
|
+
timestamp: this.timestamp,
|
|
714
|
+
platform: this.platform,
|
|
715
|
+
detection: this.detection,
|
|
716
|
+
registeredProviders: this.registeredProviders,
|
|
717
|
+
providerTests: this.providerTests,
|
|
718
|
+
recommendedProvider: this.recommendedProvider,
|
|
719
|
+
warnings: this.warnings,
|
|
720
|
+
recommendations: this.recommendations,
|
|
721
|
+
},
|
|
722
|
+
null,
|
|
723
|
+
2
|
|
724
|
+
);
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
return diagnostics;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Compare performance of multiple providers
|
|
733
|
+
*
|
|
734
|
+
* @param providers - Array of provider types to compare
|
|
735
|
+
* @param iterations - Number of iterations for each benchmark (default: 10)
|
|
736
|
+
* @param logger - Optional logger for debug output
|
|
737
|
+
* @returns Array of benchmark results for comparison
|
|
738
|
+
*
|
|
739
|
+
* @example
|
|
740
|
+
* ```typescript
|
|
741
|
+
* const results = await compareProviders(['webcrypto', 'noble'], 20);
|
|
742
|
+
* results.forEach(result => {
|
|
743
|
+
* console.log(`\n${result.provider}:`);
|
|
744
|
+
* console.log(` ECDH: ${result.ecdh.avgMs.toFixed(2)}ms`);
|
|
745
|
+
* console.log(` Random: ${result.randomBytes.avgMs.toFixed(2)}ms`);
|
|
746
|
+
* console.log(` AES: ${result.aesGcm.avgMs.toFixed(2)}ms`);
|
|
747
|
+
* });
|
|
748
|
+
* ```
|
|
749
|
+
*/
|
|
750
|
+
export async function compareProviders(
|
|
751
|
+
providers: CryptoProviderType[],
|
|
752
|
+
iterations: number = 10,
|
|
753
|
+
logger?: Logger
|
|
754
|
+
): Promise<ProviderBenchmark[]> {
|
|
755
|
+
const results: ProviderBenchmark[] = [];
|
|
756
|
+
|
|
757
|
+
for (const provider of providers) {
|
|
758
|
+
try {
|
|
759
|
+
const benchmark = await benchmarkProvider(provider, iterations, logger);
|
|
760
|
+
results.push(benchmark);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
logger?.warn(`Failed to benchmark ${provider}:`, {
|
|
763
|
+
error: {
|
|
764
|
+
message: error instanceof Error ? error.message : String(error),
|
|
765
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
766
|
+
},
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return results;
|
|
772
|
+
}
|