@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,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod validation schemas for BananaLink protocol types
|
|
3
|
+
* Provides runtime type validation for all protocol messages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { FRUITS, NETWORK_CONFIG } from '../constants';
|
|
8
|
+
|
|
9
|
+
// Base validation schemas
|
|
10
|
+
const ethereumAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address');
|
|
11
|
+
const iso8601Schema = z.string().datetime({ message: 'Invalid ISO 8601 datetime' });
|
|
12
|
+
const urlSchema = z.string().url('Invalid URL');
|
|
13
|
+
const domainSchema = z.string().min(1, 'Domain cannot be empty');
|
|
14
|
+
// Protocol v2.0: 32-byte nonces = 64 hex characters
|
|
15
|
+
const nonceSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.length(64, 'Nonce must be exactly 64 characters (32 bytes hex-encoded)')
|
|
18
|
+
.regex(/^[a-fA-F0-9]{64}$/, 'Nonce must be a valid 64-character hexadecimal string');
|
|
19
|
+
|
|
20
|
+
// SIWE base schema
|
|
21
|
+
export const siweFieldsSchema = z.object({
|
|
22
|
+
scheme: z.string().optional(),
|
|
23
|
+
domain: domainSchema,
|
|
24
|
+
address: ethereumAddressSchema,
|
|
25
|
+
statement: z.string().optional(),
|
|
26
|
+
uri: urlSchema,
|
|
27
|
+
version: z.literal('1'),
|
|
28
|
+
chainId: z.number().int().positive(),
|
|
29
|
+
nonce: nonceSchema,
|
|
30
|
+
issuedAt: iso8601Schema,
|
|
31
|
+
expirationTime: iso8601Schema.optional(),
|
|
32
|
+
notBefore: iso8601Schema.optional(),
|
|
33
|
+
requestId: z.string().optional(),
|
|
34
|
+
resources: z.array(urlSchema).optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// DApp metadata schema
|
|
38
|
+
export const dAppMetadataSchema = z.object({
|
|
39
|
+
returnUrl: urlSchema.optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// QR payload schema
|
|
43
|
+
export const qrPayloadSchema = z.object({
|
|
44
|
+
sessionId: z.string().min(1, 'Session ID cannot be empty'),
|
|
45
|
+
publicKey: z.string().min(1, 'Public key cannot be empty'),
|
|
46
|
+
providerId: z.string().optional(),
|
|
47
|
+
relayUrl: z.string().url().optional(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Encrypted payload schema
|
|
51
|
+
export const encryptedPayloadSchema = z.object({
|
|
52
|
+
iv: z.string().min(1, 'IV cannot be empty'),
|
|
53
|
+
ciphertext: z.string().min(1, 'Ciphertext cannot be empty'),
|
|
54
|
+
mac: z.string().min(1, 'MAC cannot be empty'),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Relay message type schema
|
|
58
|
+
export const relayMessageTypeSchema = z.enum(['pub', 'sub', 'ack', 'error']);
|
|
59
|
+
|
|
60
|
+
// Relay message schema
|
|
61
|
+
export const relayMessageSchema = z.object({
|
|
62
|
+
topic: z.string().min(1, 'Topic cannot be empty'),
|
|
63
|
+
type: relayMessageTypeSchema,
|
|
64
|
+
payload: encryptedPayloadSchema.optional(),
|
|
65
|
+
id: z.string().min(1, 'Message ID cannot be empty'),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Security policy schema
|
|
69
|
+
export const securityPolicySchema = z.object({
|
|
70
|
+
sessionTimeout: z.number().int().positive(),
|
|
71
|
+
requestTimeout: z.number().int().positive(),
|
|
72
|
+
maxConcurrentSessions: z.number().int().positive(),
|
|
73
|
+
requireOriginProof: z.boolean(),
|
|
74
|
+
allowedDomains: z.array(domainSchema).optional(),
|
|
75
|
+
blockedDomains: z.array(domainSchema).optional(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Origin proof schema
|
|
79
|
+
export const originProofSchema = z.object({
|
|
80
|
+
domain: domainSchema,
|
|
81
|
+
timestamp: z.number().int().positive(),
|
|
82
|
+
signature: z.string().min(1, 'Signature cannot be empty'),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Session state schema (Protocol v2.0)
|
|
86
|
+
export const sessionStateSchema = z.enum([
|
|
87
|
+
'created',
|
|
88
|
+
'claimed',
|
|
89
|
+
'authenticated',
|
|
90
|
+
'active',
|
|
91
|
+
'disconnected',
|
|
92
|
+
'closed',
|
|
93
|
+
'expired',
|
|
94
|
+
'rejected',
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
// Session info schema
|
|
98
|
+
export const sessionInfoSchema = z.object({
|
|
99
|
+
sessionId: z.string().min(1, 'Session ID cannot be empty'),
|
|
100
|
+
state: sessionStateSchema,
|
|
101
|
+
address: ethereumAddressSchema.optional(),
|
|
102
|
+
metadata: dAppMetadataSchema,
|
|
103
|
+
createdAt: iso8601Schema,
|
|
104
|
+
lastActivity: iso8601Schema,
|
|
105
|
+
expiresAt: iso8601Schema.optional(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Display info schema
|
|
109
|
+
export const displayInfoSchema = z.object({
|
|
110
|
+
qrCode: z.string().min(1, 'QR code cannot be empty'),
|
|
111
|
+
frutiLink: z.string().regex(/^[🍌🍎🍊🍇🍓🍑🍒🍉🥝🍍🥭🥥🫐🍈🍋🥑]{8}$/u, 'Invalid FrutiLink format'),
|
|
112
|
+
deepLink: urlSchema,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Error code schema (Protocol v2.0+)
|
|
116
|
+
export const errorCodeSchema = z.enum([
|
|
117
|
+
// Session errors
|
|
118
|
+
'SESSION_EXPIRED',
|
|
119
|
+
'SESSION_NOT_FOUND',
|
|
120
|
+
'SESSION_ALREADY_CLAIMED',
|
|
121
|
+
'INVALID_SESSION_CLAIM',
|
|
122
|
+
'INVALID_CLIENT_CLAIM',
|
|
123
|
+
'SESSION_TOKEN_EXPIRED',
|
|
124
|
+
'SESSION_CLOSED',
|
|
125
|
+
// Encryption errors
|
|
126
|
+
'ENCRYPTION_FAILED',
|
|
127
|
+
'DECRYPTION_FAILED',
|
|
128
|
+
'INVALID_SIGNATURE',
|
|
129
|
+
// Network errors
|
|
130
|
+
'NETWORK_ERROR',
|
|
131
|
+
'RELAY_ERROR',
|
|
132
|
+
'MESSAGE_QUEUE_FULL',
|
|
133
|
+
'RATE_LIMIT_EXCEEDED',
|
|
134
|
+
'ORIGIN_MISMATCH',
|
|
135
|
+
// Request lifecycle errors (v2.1+)
|
|
136
|
+
'REQUEST_PENDING',
|
|
137
|
+
'REQUEST_NOT_FOUND',
|
|
138
|
+
'REQUEST_EXPIRED',
|
|
139
|
+
'REQUEST_INVALID',
|
|
140
|
+
'REQUEST_TIMEOUT',
|
|
141
|
+
// Operation-specific errors (v2.1+)
|
|
142
|
+
'SIGNING_FAILED',
|
|
143
|
+
'SIGNING_REJECTED',
|
|
144
|
+
'TRANSACTION_INVALID',
|
|
145
|
+
'CHAIN_MISMATCH',
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
// BananaLink error schema
|
|
149
|
+
export const bananaLinkErrorSchema = z.object({
|
|
150
|
+
code: errorCodeSchema,
|
|
151
|
+
message: z.string().min(1, 'Error message cannot be empty'),
|
|
152
|
+
details: z.unknown().optional(),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// FrutiLink validation
|
|
156
|
+
export const frutiLinkSchema = z.string()
|
|
157
|
+
.length(8, 'FrutiLink must be exactly 8 characters')
|
|
158
|
+
.refine(
|
|
159
|
+
(value) => value.split('').every(char => FRUITS.includes(char as typeof FRUITS[number])),
|
|
160
|
+
'FrutiLink contains invalid fruits'
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Supported chain ID validation
|
|
164
|
+
export const supportedChainIdSchema = z.number()
|
|
165
|
+
.int()
|
|
166
|
+
.refine(
|
|
167
|
+
(chainId) => NETWORK_CONFIG.SUPPORTED_CHAINS.includes(chainId as typeof NETWORK_CONFIG.SUPPORTED_CHAINS[number]),
|
|
168
|
+
'Unsupported chain ID'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Base64 string validation
|
|
172
|
+
export const base64Schema = z.string().regex(
|
|
173
|
+
/^[A-Za-z0-9+/]*={0,2}$/,
|
|
174
|
+
'Invalid Base64 string'
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Session ID validation (UUID v4)
|
|
178
|
+
export const sessionIdSchema = z.string().regex(
|
|
179
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
|
|
180
|
+
'Invalid session ID format'
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Public key validation (base64 encoded)
|
|
184
|
+
export const publicKeySchema = z.string()
|
|
185
|
+
.min(1, 'Public key cannot be empty')
|
|
186
|
+
.refine(
|
|
187
|
+
(key) => {
|
|
188
|
+
try {
|
|
189
|
+
atob(key);
|
|
190
|
+
return true;
|
|
191
|
+
} catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
'Public key must be valid Base64'
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Validation helper functions (Protocol v2.0)
|
|
199
|
+
export const validateQRPayload = (payload: unknown) => qrPayloadSchema.parse(payload);
|
|
200
|
+
export const validateRelayMessage = (message: unknown) => relayMessageSchema.parse(message);
|
|
201
|
+
export const validateSessionInfo = (info: unknown) => sessionInfoSchema.parse(info);
|
|
202
|
+
export const validateFrutiLink = (frutiLink: unknown) => frutiLinkSchema.parse(frutiLink);
|
|
203
|
+
|
|
204
|
+
// Safe parsing functions (returns { success: boolean, data?: T, error?: ZodError })
|
|
205
|
+
export const safeValidateQRPayload = (payload: unknown) => qrPayloadSchema.safeParse(payload);
|
|
206
|
+
export const safeValidateRelayMessage = (message: unknown) => relayMessageSchema.safeParse(message);
|
|
207
|
+
export const safeValidateSessionInfo = (info: unknown) => sessionInfoSchema.safeParse(info);
|
|
208
|
+
export const safeValidateFrutiLink = (frutiLink: unknown) => frutiLinkSchema.safeParse(frutiLink);
|
|
209
|
+
|
|
210
|
+
// Type inference from schemas (Protocol v2.0)
|
|
211
|
+
export type SIWEFields = z.infer<typeof siweFieldsSchema>;
|
|
212
|
+
export type DAppMetadata = z.infer<typeof dAppMetadataSchema>;
|
|
213
|
+
export type QRPayload = z.infer<typeof qrPayloadSchema>;
|
|
214
|
+
export type EncryptedPayload = z.infer<typeof encryptedPayloadSchema>;
|
|
215
|
+
export type RelayMessage = z.infer<typeof relayMessageSchema>;
|
|
216
|
+
export type SecurityPolicy = z.infer<typeof securityPolicySchema>;
|
|
217
|
+
export type SessionInfo = z.infer<typeof sessionInfoSchema>;
|
|
218
|
+
export type DisplayInfo = z.infer<typeof displayInfoSchema>;
|
|
219
|
+
|
|
220
|
+
// Re-export core schemas (Protocol v2.0)
|
|
221
|
+
export {
|
|
222
|
+
dAppMetadataSchema as coreDAppMetadataSchema,
|
|
223
|
+
sessionConfigSchema,
|
|
224
|
+
sessionOptionsSchema,
|
|
225
|
+
sessionMetadataSchema,
|
|
226
|
+
createSessionRequestSchema,
|
|
227
|
+
createSessionResponseSchema,
|
|
228
|
+
clientSessionClaimSchema,
|
|
229
|
+
securityPolicySchema as coreSecurityPolicySchema,
|
|
230
|
+
} from './core';
|
|
231
|
+
|
|
232
|
+
// Re-export v2.0 wallet message schemas
|
|
233
|
+
export * from './wallet-messages';
|
|
234
|
+
|
|
235
|
+
// Re-export v2.0 client message schemas
|
|
236
|
+
export * from './client-messages';
|
|
237
|
+
|
|
238
|
+
// Re-export v2.0 relay message schemas
|
|
239
|
+
export * from './relay-messages';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation schemas for relay notification message types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
// Session closed acknowledgment schema
|
|
8
|
+
export const sessionClosedAckSchema = z.object({
|
|
9
|
+
type: z.literal('session_closed_ack'),
|
|
10
|
+
timestamp: z.number().int().positive(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Session closed notification schema
|
|
14
|
+
export const sessionClosedNotificationSchema = z.object({
|
|
15
|
+
type: z.literal('session_closed_notification'),
|
|
16
|
+
/** Which party closed the session */
|
|
17
|
+
closedBy: z.enum(['client', 'wallet']),
|
|
18
|
+
/** Optional reason for closure */
|
|
19
|
+
reason: z.string().optional(),
|
|
20
|
+
timestamp: z.number().int().positive(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Reconnected message schema
|
|
24
|
+
export const reconnectedMessageSchema = z.object({
|
|
25
|
+
type: z.literal('reconnected'),
|
|
26
|
+
/** Number of messages that were queued during disconnection */
|
|
27
|
+
queuedMessages: z.number().int().nonnegative(),
|
|
28
|
+
timestamp: z.number().int().positive(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Union of all relay notification messages
|
|
32
|
+
export const relayNotificationMessageSchema = z.discriminatedUnion('type', [
|
|
33
|
+
sessionClosedAckSchema,
|
|
34
|
+
sessionClosedNotificationSchema,
|
|
35
|
+
reconnectedMessageSchema,
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// Validation helper functions
|
|
39
|
+
export const validateRelayNotificationMessage = (message: unknown) => {
|
|
40
|
+
return relayNotificationMessageSchema.parse(message);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const safeValidateRelayNotificationMessage = (message: unknown) => {
|
|
44
|
+
return relayNotificationMessageSchema.safeParse(message);
|
|
45
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation schemas for wallet message types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { dAppMetadataSchema, sessionConfigSchema } from './core';
|
|
7
|
+
import { encryptedPayloadSchema } from './discovery';
|
|
8
|
+
|
|
9
|
+
// Re-export encryptedPayloadSchema for convenience
|
|
10
|
+
export { encryptedPayloadSchema };
|
|
11
|
+
|
|
12
|
+
// Wallet session claim schema
|
|
13
|
+
export const walletSessionClaimSchema = z.object({
|
|
14
|
+
/** Unique nonce for this session claim (32 bytes hex-encoded = 64 characters) */
|
|
15
|
+
sessionNonce: z
|
|
16
|
+
.string()
|
|
17
|
+
.length(64, 'Session nonce must be exactly 64 characters (32 bytes hex-encoded)')
|
|
18
|
+
.regex(/^[0-9a-f]{64}$/, 'Session nonce must be lowercase hexadecimal'),
|
|
19
|
+
/** Claim timestamp */
|
|
20
|
+
timestamp: z.number().int().positive('Timestamp must be a positive integer'),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Wallet message payload schemas
|
|
24
|
+
export const claimSessionPayloadSchema = z.object({
|
|
25
|
+
type: z.literal('claim_session'),
|
|
26
|
+
/** Wallet's public key for ECDH key exchange - format: "algorithm:key_data" (e.g., "AES-GCM:base64..." or "cleartext:none") */
|
|
27
|
+
walletPublicKey: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1, 'Wallet public key cannot be empty')
|
|
30
|
+
.regex(/^(AES-GCM:|cleartext:)/, 'Wallet public key must start with "AES-GCM:" or "cleartext:"'),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const prefetchMetadataPayloadSchema = z.object({
|
|
34
|
+
type: z.literal('prefetch_metadata'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const connectionRejectedPayloadSchema = z.object({
|
|
38
|
+
type: z.literal('connection_rejected'),
|
|
39
|
+
encrypted: encryptedPayloadSchema,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const authenticateConnectionPayloadSchema = z.object({
|
|
43
|
+
type: z.literal('authenticate_connection'),
|
|
44
|
+
encrypted: encryptedPayloadSchema,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const walletReconnectPayloadSchema = z.object({
|
|
48
|
+
type: z.literal('wallet_reconnect'),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Union of all wallet message payloads
|
|
52
|
+
export const walletMessagePayloadSchema = z.discriminatedUnion('type', [
|
|
53
|
+
claimSessionPayloadSchema,
|
|
54
|
+
prefetchMetadataPayloadSchema,
|
|
55
|
+
connectionRejectedPayloadSchema,
|
|
56
|
+
authenticateConnectionPayloadSchema,
|
|
57
|
+
walletReconnectPayloadSchema,
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
// Wallet message envelope schema
|
|
61
|
+
export const walletMessageEnvelopeSchema = z.object({
|
|
62
|
+
/** Session identifier */
|
|
63
|
+
sessionId: z.string().uuid('Session ID must be a valid UUID'),
|
|
64
|
+
/** Wallet session claim credential */
|
|
65
|
+
walletSessionClaim: walletSessionClaimSchema,
|
|
66
|
+
/** Message payload */
|
|
67
|
+
payload: walletMessagePayloadSchema,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Decrypted data schemas
|
|
71
|
+
export const rejectionDataSchema = z.object({
|
|
72
|
+
reason: z.string().optional(),
|
|
73
|
+
timestamp: z.number().int().positive(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const walletMetadataSchema = z.object({
|
|
77
|
+
/** Wallet name */
|
|
78
|
+
name: z.string().min(1, 'Wallet name cannot be empty'),
|
|
79
|
+
/** Wallet version */
|
|
80
|
+
version: z.string().optional(),
|
|
81
|
+
/** Wallet icon URL */
|
|
82
|
+
icon: z.string().url('Invalid icon URL').optional(),
|
|
83
|
+
/** Wallet description */
|
|
84
|
+
description: z.string().optional(),
|
|
85
|
+
/** Wallet website URL */
|
|
86
|
+
url: z.string().url('Invalid wallet URL').optional(),
|
|
87
|
+
/** Wallet provider type */
|
|
88
|
+
provider: z.string().optional(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const authenticationDataSchema = z.object({
|
|
92
|
+
/** Wallet address */
|
|
93
|
+
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address'),
|
|
94
|
+
/** Signature of the SIWE message */
|
|
95
|
+
signature: z.string().regex(/^0x[a-fA-F0-9]+$/, 'Invalid signature format'),
|
|
96
|
+
/** Full SIWE message that was signed */
|
|
97
|
+
message: z.string().min(1, 'Message cannot be empty'),
|
|
98
|
+
/** Chain ID (EIP-155) */
|
|
99
|
+
chainId: z.number().int().positive('Chain ID must be a positive integer'),
|
|
100
|
+
/** Network identifier */
|
|
101
|
+
network: z.string().optional(),
|
|
102
|
+
/** Signing algorithm */
|
|
103
|
+
signingAlgo: z.string().optional(),
|
|
104
|
+
/** Optional wallet metadata */
|
|
105
|
+
walletMetadata: walletMetadataSchema.optional(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Relay-to-dApp message schemas
|
|
109
|
+
export const walletHandshakeMessageSchema = z.object({
|
|
110
|
+
type: z.literal('wallet_handshake'),
|
|
111
|
+
status: z.literal('reviewing'),
|
|
112
|
+
/** Wallet's public key in format "algorithm:base64_public_key" */
|
|
113
|
+
walletPublicKey: z.string(),
|
|
114
|
+
timestamp: z.number().int().positive(),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export const connectionRejectedMessageSchema = z.object({
|
|
118
|
+
type: z.literal('connection_rejected'),
|
|
119
|
+
encrypted: encryptedPayloadSchema,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export const connectionAuthenticatedMessageSchema = z.object({
|
|
123
|
+
type: z.literal('connection_authenticated'),
|
|
124
|
+
encrypted: encryptedPayloadSchema,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Union of relay-to-dApp messages
|
|
128
|
+
export const relayToDAppMessageSchema = z.discriminatedUnion('type', [
|
|
129
|
+
walletHandshakeMessageSchema,
|
|
130
|
+
connectionRejectedMessageSchema,
|
|
131
|
+
connectionAuthenticatedMessageSchema,
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
// Prefetch metadata response schema
|
|
135
|
+
export const prefetchMetadataResponseSchema = z.object({
|
|
136
|
+
dappMetadata: dAppMetadataSchema,
|
|
137
|
+
sessionConfig: sessionConfigSchema.optional(),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Validation helper functions
|
|
141
|
+
export const validateWalletMessage = (message: unknown) => {
|
|
142
|
+
return walletMessageEnvelopeSchema.parse(message);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const safeValidateWalletMessage = (message: unknown) => {
|
|
146
|
+
return walletMessageEnvelopeSchema.safeParse(message);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const validateRelayToDAppMessage = (message: unknown) => {
|
|
150
|
+
return relayToDAppMessageSchema.parse(message);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const safeValidateRelayToDAppMessage = (message: unknown) => {
|
|
154
|
+
return relayToDAppMessageSchema.safeParse(message);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const validateWalletSessionClaim = (claim: unknown) => {
|
|
158
|
+
return walletSessionClaimSchema.parse(claim);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const safeValidateWalletSessionClaim = (claim: unknown) => {
|
|
162
|
+
return walletSessionClaimSchema.safeParse(claim);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Validate timestamp for replay attack prevention
|
|
167
|
+
* @param timestamp - Timestamp to validate
|
|
168
|
+
* @param maxAge - Maximum age in milliseconds (default: 5 minutes)
|
|
169
|
+
*/
|
|
170
|
+
export function validateTimestamp(timestamp: number, maxAge: number = 5 * 60 * 1000): boolean {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
const age = Math.abs(now - timestamp);
|
|
173
|
+
return age <= maxAge;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// NOTE: Nonce generation moved to @bananalink-sdk/crypto package to avoid circular dependencies
|
|
177
|
+
// SDKs should use crypto.generateSessionNonce() before validating session claims
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod validation schemas for BananaLink protocol
|
|
3
|
+
*
|
|
4
|
+
* This module exports all runtime validation schemas for the BananaLink protocol.
|
|
5
|
+
* Import from '@bananalink-sdk/protocol/schemas' when you need runtime validation.
|
|
6
|
+
*
|
|
7
|
+
* For TypeScript type definitions only, import from '@bananalink-sdk/protocol' instead.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Runtime validation
|
|
12
|
+
* import { sessionStateSchema, validateMessage } from '@bananalink-sdk/protocol/schemas';
|
|
13
|
+
*
|
|
14
|
+
* // Type-only imports
|
|
15
|
+
* import type { SessionState, BananaLinkMessage } from '@bananalink-sdk/protocol';
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Re-export all schemas via the main schemas index
|
|
22
|
+
// (which already aggregates all schema modules)
|
|
23
|
+
export * from './schemas';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIWE (Sign-In with Ethereum) utilities for BananaLink protocol
|
|
3
|
+
*
|
|
4
|
+
* This module exports ERC-4361 compliant message construction and validation utilities.
|
|
5
|
+
* Import from '@bananalink-sdk/protocol/siwe' when you need SIWE message handling.
|
|
6
|
+
*
|
|
7
|
+
* Most SDKs and applications won't need this module unless implementing custom
|
|
8
|
+
* authentication flows or wallet integration at the protocol level.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // SIWE message construction and validation
|
|
13
|
+
* import {
|
|
14
|
+
* constructSIWEMessage,
|
|
15
|
+
* parseSIWEMessage,
|
|
16
|
+
* validateSIWEMessage
|
|
17
|
+
* } from '@bananalink-sdk/protocol/siwe';
|
|
18
|
+
*
|
|
19
|
+
* // Type-only imports (available from main export)
|
|
20
|
+
* import type { SIWEFields, SIWEMessageOptions } from '@bananalink-sdk/protocol';
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @packageDocumentation
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// Re-export all SIWE utilities
|
|
27
|
+
export * from './utils/siwe';
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testing utilities for @bananalink-sdk/protocol
|
|
3
|
+
*
|
|
4
|
+
* This module provides test utilities and mock implementations
|
|
5
|
+
* for testing BananaLink protocol integrations.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { WalletSessionClaim } from './types/wallet-messages';
|
|
11
|
+
import type { ClientSessionClaim } from './types/client-messages';
|
|
12
|
+
|
|
13
|
+
// Re-export storage interfaces
|
|
14
|
+
export type { WalletSessionClaimStorage } from './utils/wallet-session-claim';
|
|
15
|
+
export type { ClientSessionClaimStorage } from './utils/client-session-claim';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* In-memory wallet session claim storage (for testing/development)
|
|
19
|
+
*
|
|
20
|
+
* WARNING: Do not use in production - claims should persist across app restarts
|
|
21
|
+
*/
|
|
22
|
+
export class InMemoryWalletSessionClaimStorage {
|
|
23
|
+
private claims: Map<string, WalletSessionClaim> = new Map();
|
|
24
|
+
|
|
25
|
+
store(sessionId: string, claim: WalletSessionClaim): Promise<void> {
|
|
26
|
+
this.claims.set(sessionId, claim);
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
retrieve(sessionId: string): Promise<WalletSessionClaim | null> {
|
|
31
|
+
return Promise.resolve(this.claims.get(sessionId) || null);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
remove(sessionId: string): Promise<void> {
|
|
35
|
+
this.claims.delete(sessionId);
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
clear(): Promise<void> {
|
|
40
|
+
this.claims.clear();
|
|
41
|
+
return Promise.resolve();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* In-memory client session claim storage (for testing/development)
|
|
47
|
+
*
|
|
48
|
+
* WARNING: Do not use in production - claims should persist across page reloads
|
|
49
|
+
*/
|
|
50
|
+
export class InMemoryClientSessionClaimStorage {
|
|
51
|
+
private claims: Map<string, ClientSessionClaim> = new Map();
|
|
52
|
+
|
|
53
|
+
store(sessionId: string, claim: ClientSessionClaim): Promise<void> {
|
|
54
|
+
this.claims.set(sessionId, claim);
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
retrieve(sessionId: string): Promise<ClientSessionClaim | null> {
|
|
59
|
+
return Promise.resolve(this.claims.get(sessionId) || null);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
remove(sessionId: string): Promise<void> {
|
|
63
|
+
this.claims.delete(sessionId);
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
clear(): Promise<void> {
|
|
68
|
+
this.claims.clear();
|
|
69
|
+
return Promise.resolve();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication types for the BananaLink protocol
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Import to ensure global type declarations are available
|
|
6
|
+
import './index';
|
|
7
|
+
|
|
8
|
+
// Session state
|
|
9
|
+
export type SessionState =
|
|
10
|
+
| 'created' // Session created by API Gateway
|
|
11
|
+
| 'claimed' // Wallet claimed session
|
|
12
|
+
| 'authenticated' // Wallet authenticated successfully
|
|
13
|
+
| 'active' // Both parties connected and communicating
|
|
14
|
+
| 'disconnected' // One or both parties temporarily disconnected
|
|
15
|
+
| 'closed' // Gracefully terminated via close_session
|
|
16
|
+
| 'expired' // TTL exceeded without activity
|
|
17
|
+
| 'rejected'; // Wallet explicitly rejected connection
|
|
18
|
+
|
|
19
|
+
// Session information
|
|
20
|
+
export interface SessionInfo {
|
|
21
|
+
/** Session identifier */
|
|
22
|
+
sessionId: string;
|
|
23
|
+
/** Current state */
|
|
24
|
+
state: SessionState;
|
|
25
|
+
/** Connected address (if any) */
|
|
26
|
+
address?: string;
|
|
27
|
+
/** DApp identifier */
|
|
28
|
+
dappId: string;
|
|
29
|
+
/** Essential dApp info */
|
|
30
|
+
dappInfo: Pick<DAppMetadata, 'name' | 'url'>;
|
|
31
|
+
/** Session-specific configuration */
|
|
32
|
+
sessionConfig?: SessionConfig;
|
|
33
|
+
/** Creation timestamp */
|
|
34
|
+
createdAt: string;
|
|
35
|
+
/** Last activity timestamp */
|
|
36
|
+
lastActivity: string;
|
|
37
|
+
/** Expiration timestamp */
|
|
38
|
+
expiresAt?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Session security configuration
|
|
42
|
+
export interface SessionSecurity {
|
|
43
|
+
/** Shared secret for encryption */
|
|
44
|
+
sharedSecret: CryptoKey;
|
|
45
|
+
/** Session identifier */
|
|
46
|
+
sessionId: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Origin proof for domain verification
|
|
50
|
+
export interface OriginProof {
|
|
51
|
+
/** Domain asserting ownership */
|
|
52
|
+
domain: string;
|
|
53
|
+
/** Timestamp of the proof */
|
|
54
|
+
timestamp: number;
|
|
55
|
+
/** Signature proving domain ownership */
|
|
56
|
+
signature: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Import types from core
|
|
60
|
+
import type { DAppMetadata, SessionConfig } from './core';
|