@bandeira-tech/b3nd-web 0.2.5 → 0.2.6

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.
@@ -1,3 +1,15 @@
1
+ import {
2
+ WalletServerCore
3
+ } from "./chunk-F3W5GZU6.js";
4
+ import {
5
+ exportPrivateKeyPem,
6
+ generateEncryptionKeyPair,
7
+ generateSigningKeyPair
8
+ } from "./chunk-JN75UL5C.js";
9
+ import {
10
+ MemoryClient
11
+ } from "./chunk-GIMIWVPX.js";
12
+
1
13
  // wallet/client.ts
2
14
  var WalletClient = class {
3
15
  constructor(config) {
@@ -381,6 +393,365 @@ var WalletClient = class {
381
393
  }
382
394
  };
383
395
 
396
+ // wallet/memory-client.ts
397
+ async function generateTestServerKeys() {
398
+ const identityKeyPair = await generateSigningKeyPair();
399
+ const encryptionKeyPair = await generateEncryptionKeyPair();
400
+ const identityPrivateKeyPem = await exportPrivateKeyPem(
401
+ identityKeyPair.privateKey,
402
+ "PRIVATE KEY"
403
+ );
404
+ const encryptionPrivateKeyBytes = new Uint8Array(
405
+ await crypto.subtle.exportKey("pkcs8", encryptionKeyPair.privateKey)
406
+ );
407
+ const encryptionBase64 = btoa(
408
+ String.fromCharCode(...encryptionPrivateKeyBytes)
409
+ );
410
+ const encryptionPrivateKeyPem = `-----BEGIN PRIVATE KEY-----
411
+ ${encryptionBase64.match(/.{1,64}/g)?.join("\n")}
412
+ -----END PRIVATE KEY-----`;
413
+ return {
414
+ identityKey: {
415
+ privateKeyPem: identityPrivateKeyPem,
416
+ publicKeyHex: identityKeyPair.publicKeyHex
417
+ },
418
+ encryptionKey: {
419
+ privateKeyPem: encryptionPrivateKeyPem,
420
+ publicKeyHex: encryptionKeyPair.publicKeyHex
421
+ }
422
+ };
423
+ }
424
+ function createWalletMemoryClient() {
425
+ return new MemoryClient({
426
+ schema: {
427
+ "immutable://accounts": async () => ({ valid: true }),
428
+ "mutable://accounts": async () => ({ valid: true })
429
+ }
430
+ });
431
+ }
432
+ var MemoryWalletClient = class _MemoryWalletClient {
433
+ constructor(server) {
434
+ this.currentSession = null;
435
+ this.apiBasePath = "/api/v1";
436
+ this.server = server;
437
+ this.handler = server.getFetchHandler();
438
+ }
439
+ /**
440
+ * Create a new MemoryWalletClient
441
+ * Use this factory method instead of constructor (async key generation)
442
+ */
443
+ static async create(config = {}) {
444
+ const serverKeys = config.serverKeys || await generateTestServerKeys();
445
+ const storageClient = config.storageClient || createWalletMemoryClient();
446
+ const serverConfig = {
447
+ serverKeys,
448
+ jwtSecret: config.jwtSecret || "test-jwt-secret-for-memory-wallet-client!!",
449
+ jwtExpirationSeconds: config.jwtExpirationSeconds || 3600,
450
+ deps: {
451
+ credentialClient: storageClient,
452
+ proxyClient: storageClient,
453
+ logger: {
454
+ log: () => {
455
+ },
456
+ warn: () => {
457
+ },
458
+ error: () => {
459
+ }
460
+ }
461
+ }
462
+ };
463
+ const server = new WalletServerCore(serverConfig);
464
+ return new _MemoryWalletClient(server);
465
+ }
466
+ /**
467
+ * Make a request to the embedded server
468
+ */
469
+ async request(method, path, body, headers) {
470
+ const url = `http://memory-wallet${this.apiBasePath}${path}`;
471
+ const requestHeaders = {
472
+ ...headers
473
+ };
474
+ if (body) {
475
+ requestHeaders["Content-Type"] = "application/json";
476
+ }
477
+ const request = new Request(url, {
478
+ method,
479
+ headers: requestHeaders,
480
+ body: body ? JSON.stringify(body) : void 0
481
+ });
482
+ return await this.handler(request);
483
+ }
484
+ /**
485
+ * Make an authenticated request
486
+ */
487
+ async authRequest(method, path, body) {
488
+ if (!this.currentSession) {
489
+ throw new Error("Not authenticated. Please login first.");
490
+ }
491
+ return this.request(method, path, body, {
492
+ Authorization: `Bearer ${this.currentSession.token}`
493
+ });
494
+ }
495
+ // ============================================================
496
+ // Session Management (same as WalletClient)
497
+ // ============================================================
498
+ getSession() {
499
+ return this.currentSession;
500
+ }
501
+ setSession(session) {
502
+ this.currentSession = session;
503
+ }
504
+ isAuthenticated() {
505
+ return this.currentSession !== null;
506
+ }
507
+ getUsername() {
508
+ return this.currentSession?.username || null;
509
+ }
510
+ getToken() {
511
+ return this.currentSession?.token || null;
512
+ }
513
+ logout() {
514
+ this.currentSession = null;
515
+ }
516
+ // ============================================================
517
+ // Health & Server Info
518
+ // ============================================================
519
+ async health() {
520
+ const response = await this.request("GET", "/health");
521
+ if (!response.ok) {
522
+ throw new Error(`Health check failed: ${response.statusText}`);
523
+ }
524
+ return await response.json();
525
+ }
526
+ async getServerKeys() {
527
+ const response = await this.request("GET", "/server-keys");
528
+ if (!response.ok) {
529
+ throw new Error(`Failed to get server keys: ${response.statusText}`);
530
+ }
531
+ const data = await response.json();
532
+ if (!data.success) {
533
+ throw new Error(data.error || "Failed to get server keys");
534
+ }
535
+ return {
536
+ identityPublicKeyHex: data.identityPublicKeyHex,
537
+ encryptionPublicKeyHex: data.encryptionPublicKeyHex
538
+ };
539
+ }
540
+ // ============================================================
541
+ // Authentication
542
+ // ============================================================
543
+ async signup(_credentials) {
544
+ throw new Error("Use signupWithToken(appKey, credentials) \u2014 app token required");
545
+ }
546
+ async login(_credentials) {
547
+ throw new Error("Use loginWithTokenSession(appKey, session, credentials) \u2014 app token + session required");
548
+ }
549
+ async signupWithToken(appKey, tokenOrCredentials, maybeCredentials) {
550
+ const credentials = typeof tokenOrCredentials === "string" ? maybeCredentials : tokenOrCredentials;
551
+ if (!credentials) throw new Error("credentials are required");
552
+ const response = await this.request("POST", `/auth/signup/${appKey}`, {
553
+ token: typeof tokenOrCredentials === "string" ? tokenOrCredentials : void 0,
554
+ type: "password",
555
+ username: credentials.username,
556
+ password: credentials.password
557
+ });
558
+ const data = await response.json();
559
+ if (!response.ok || !data.success) {
560
+ throw new Error(data.error || `Signup failed: ${response.statusText}`);
561
+ }
562
+ return {
563
+ username: data.username,
564
+ token: data.token,
565
+ expiresIn: data.expiresIn
566
+ };
567
+ }
568
+ async loginWithTokenSession(appKey, tokenOrSession, sessionOrCredentials, maybeCredentials) {
569
+ const session = typeof sessionOrCredentials === "string" && maybeCredentials ? sessionOrCredentials : tokenOrSession;
570
+ const credentials = maybeCredentials || sessionOrCredentials;
571
+ if (!session || typeof session !== "string") throw new Error("session is required");
572
+ const response = await this.request("POST", `/auth/login/${appKey}`, {
573
+ token: typeof tokenOrSession === "string" && maybeCredentials ? tokenOrSession : void 0,
574
+ session,
575
+ type: "password",
576
+ username: credentials.username,
577
+ password: credentials.password
578
+ });
579
+ const data = await response.json();
580
+ if (!response.ok || !data.success) {
581
+ throw new Error(data.error || `Login failed: ${response.statusText}`);
582
+ }
583
+ return {
584
+ username: data.username,
585
+ token: data.token,
586
+ expiresIn: data.expiresIn
587
+ };
588
+ }
589
+ // ============================================================
590
+ // Password Management
591
+ // ============================================================
592
+ async changePassword(appKey, oldPassword, newPassword) {
593
+ const response = await this.authRequest(
594
+ "POST",
595
+ `/auth/credentials/change-password/${appKey}`,
596
+ { oldPassword, newPassword }
597
+ );
598
+ const data = await response.json();
599
+ if (!response.ok || !data.success) {
600
+ throw new Error(data.error || `Change password failed: ${response.statusText}`);
601
+ }
602
+ }
603
+ async requestPasswordReset(_username) {
604
+ throw new Error("Use requestPasswordResetWithToken(appKey, username)");
605
+ }
606
+ async resetPassword(_username, _resetToken, _newPassword) {
607
+ throw new Error("Use resetPasswordWithToken(appKey, username, resetToken, newPassword)");
608
+ }
609
+ async requestPasswordResetWithToken(appKey, tokenOrUsername, maybeUsername) {
610
+ const username = maybeUsername || tokenOrUsername;
611
+ const response = await this.request(
612
+ "POST",
613
+ `/auth/credentials/request-password-reset/${appKey}`,
614
+ { username }
615
+ );
616
+ const data = await response.json();
617
+ if (!response.ok || !data.success) {
618
+ throw new Error(data.error || `Request password reset failed: ${response.statusText}`);
619
+ }
620
+ return {
621
+ resetToken: data.resetToken,
622
+ expiresIn: data.expiresIn
623
+ };
624
+ }
625
+ async resetPasswordWithToken(appKey, _tokenOrUsername, usernameOrReset, resetToken, newPassword) {
626
+ const username = usernameOrReset;
627
+ if (!resetToken || !newPassword) {
628
+ throw new Error("resetToken and newPassword are required");
629
+ }
630
+ const response = await this.request(
631
+ "POST",
632
+ `/auth/credentials/reset-password/${appKey}`,
633
+ { username, resetToken, newPassword }
634
+ );
635
+ const data = await response.json();
636
+ if (!response.ok || !data.success) {
637
+ throw new Error(data.error || `Reset password failed: ${response.statusText}`);
638
+ }
639
+ return {
640
+ username: data.username,
641
+ token: data.token,
642
+ expiresIn: data.expiresIn
643
+ };
644
+ }
645
+ // ============================================================
646
+ // Public Keys
647
+ // ============================================================
648
+ async getPublicKeys(appKey) {
649
+ if (!appKey || typeof appKey !== "string") {
650
+ throw new Error("appKey is required");
651
+ }
652
+ const response = await this.authRequest("GET", `/auth/public-keys/${appKey}`);
653
+ const data = await response.json();
654
+ if (!response.ok || !data.success) {
655
+ throw new Error(data.error || `Get public keys failed: ${response.statusText}`);
656
+ }
657
+ return {
658
+ accountPublicKeyHex: data.accountPublicKeyHex,
659
+ encryptionPublicKeyHex: data.encryptionPublicKeyHex
660
+ };
661
+ }
662
+ async getMyPublicKeys(appKey) {
663
+ return this.getPublicKeys(appKey);
664
+ }
665
+ // ============================================================
666
+ // Proxy Operations
667
+ // ============================================================
668
+ async proxyWrite(request) {
669
+ const response = await this.authRequest("POST", "/proxy/write", {
670
+ uri: request.uri,
671
+ data: request.data,
672
+ encrypt: request.encrypt
673
+ });
674
+ const data = await response.json();
675
+ if (!response.ok || !data.success) {
676
+ throw new Error(data.error || `Proxy write failed: ${response.statusText}`);
677
+ }
678
+ return data;
679
+ }
680
+ async proxyRead(request) {
681
+ const url = `/proxy/read?uri=${encodeURIComponent(request.uri)}`;
682
+ const response = await this.authRequest("GET", url);
683
+ const data = await response.json();
684
+ if (!response.ok || !data.success) {
685
+ throw new Error(data.error || `Proxy read failed: ${response.statusText}`);
686
+ }
687
+ return data;
688
+ }
689
+ // ============================================================
690
+ // Google OAuth (for completeness - may not work without real Google)
691
+ // ============================================================
692
+ async signupWithGoogle(appKey, token, googleIdToken) {
693
+ if (!token) throw new Error("token is required");
694
+ if (!googleIdToken) throw new Error("googleIdToken is required");
695
+ const response = await this.request("POST", `/auth/signup/${appKey}`, {
696
+ token,
697
+ type: "google",
698
+ googleIdToken
699
+ });
700
+ const data = await response.json();
701
+ if (!response.ok || !data.success) {
702
+ throw new Error(data.error || `Google signup failed: ${response.statusText}`);
703
+ }
704
+ return {
705
+ username: data.username,
706
+ token: data.token,
707
+ expiresIn: data.expiresIn,
708
+ email: data.email,
709
+ name: data.name,
710
+ picture: data.picture
711
+ };
712
+ }
713
+ async loginWithGoogle(appKey, token, session, googleIdToken) {
714
+ if (!token) throw new Error("token is required");
715
+ if (!session) throw new Error("session is required");
716
+ if (!googleIdToken) throw new Error("googleIdToken is required");
717
+ const response = await this.request("POST", `/auth/login/${appKey}`, {
718
+ token,
719
+ session,
720
+ type: "google",
721
+ googleIdToken
722
+ });
723
+ const data = await response.json();
724
+ if (!response.ok || !data.success) {
725
+ throw new Error(data.error || `Google login failed: ${response.statusText}`);
726
+ }
727
+ return {
728
+ username: data.username,
729
+ token: data.token,
730
+ expiresIn: data.expiresIn,
731
+ email: data.email,
732
+ name: data.name,
733
+ picture: data.picture
734
+ };
735
+ }
736
+ // ============================================================
737
+ // Testing Utilities
738
+ // ============================================================
739
+ /**
740
+ * Get the underlying WalletServerCore (for testing/inspection)
741
+ */
742
+ getServer() {
743
+ return this.server;
744
+ }
745
+ /**
746
+ * Get server's public keys directly (convenience method)
747
+ */
748
+ getServerPublicKeys() {
749
+ return this.server.getServerKeys();
750
+ }
751
+ };
752
+
384
753
  export {
385
- WalletClient
754
+ WalletClient,
755
+ generateTestServerKeys,
756
+ MemoryWalletClient
386
757
  };
@@ -0,0 +1,202 @@
1
+ // clients/memory/mod.ts
2
+ function target(uri, schema, storage) {
3
+ const url = URL.parse(uri);
4
+ const program = `${url.protocol}//${url.hostname}`;
5
+ if (!schema[program]) {
6
+ return { success: false, error: "Program not found" };
7
+ }
8
+ const node = storage.get(program);
9
+ if (!node) {
10
+ return { success: false, error: "Storage not initialized for program" };
11
+ }
12
+ const parts = url.pathname.substring(1).split("/");
13
+ return {
14
+ success: true,
15
+ program,
16
+ path: url.pathname,
17
+ node,
18
+ parts
19
+ };
20
+ }
21
+ var MemoryClient = class {
22
+ constructor(config) {
23
+ this.schema = config.schema;
24
+ this.storage = config.storage || /* @__PURE__ */ new Map();
25
+ this.cleanup();
26
+ }
27
+ async write(uri, payload) {
28
+ const result = target(uri, this.schema, this.storage);
29
+ if (!result.success) {
30
+ return result;
31
+ }
32
+ const { program, node, parts } = result;
33
+ const validator = this.schema[program];
34
+ const validation = await validator({ uri, value: payload, read: this.read.bind(this) });
35
+ if (!validation.valid) {
36
+ return {
37
+ success: false,
38
+ error: validation.error || "Validation failed"
39
+ };
40
+ }
41
+ const record = {
42
+ ts: Date.now(),
43
+ data: payload
44
+ };
45
+ let prev = node;
46
+ parts.filter(Boolean).forEach((ns) => {
47
+ if (!prev.children) prev.children = /* @__PURE__ */ new Map();
48
+ if (!prev.children.get(ns)) {
49
+ const newnode = {};
50
+ prev.children.set(ns, newnode);
51
+ prev = newnode;
52
+ } else {
53
+ prev = prev.children.get(ns);
54
+ }
55
+ });
56
+ prev.value = record;
57
+ return {
58
+ success: true,
59
+ record
60
+ };
61
+ }
62
+ read(uri) {
63
+ const result = target(uri, this.schema, this.storage);
64
+ if (!result.success) {
65
+ return Promise.resolve(result);
66
+ }
67
+ const { node, parts } = result;
68
+ let current = node;
69
+ for (const part of parts.filter(Boolean)) {
70
+ current = current?.children?.get(part);
71
+ if (!current) {
72
+ return Promise.resolve({
73
+ success: false,
74
+ error: `Path not found: ${part}`
75
+ });
76
+ }
77
+ }
78
+ if (!current.value) {
79
+ return Promise.resolve({
80
+ success: false,
81
+ error: "Not found"
82
+ });
83
+ }
84
+ return Promise.resolve({
85
+ success: true,
86
+ record: current.value
87
+ });
88
+ }
89
+ list(uri, options) {
90
+ const result = target(uri, this.schema, this.storage);
91
+ if (!result.success) {
92
+ return Promise.resolve(result);
93
+ }
94
+ const { node, parts, program, path } = result;
95
+ let current = node;
96
+ const filteredParts = parts.filter(Boolean);
97
+ for (const part of filteredParts) {
98
+ current = current?.children?.get(part);
99
+ if (!current) {
100
+ return Promise.resolve({
101
+ success: false,
102
+ error: `Path not found: ${part}`
103
+ });
104
+ }
105
+ }
106
+ if (!current.children?.size) {
107
+ return Promise.resolve({
108
+ success: true,
109
+ data: [],
110
+ pagination: {
111
+ page: options?.page ?? 1,
112
+ limit: options?.limit ?? 50,
113
+ total: 0
114
+ }
115
+ });
116
+ }
117
+ let items = Array.from(current.children.entries()).map(([key, child]) => ({
118
+ uri: path.endsWith("/") ? `${program}://${path}${key}` : `${program}://${path}/${key}`,
119
+ type: child.value ? "file" : "directory"
120
+ }));
121
+ if (options?.pattern) {
122
+ const regex = new RegExp(options.pattern);
123
+ items = items.filter((item) => regex.test(item.uri));
124
+ }
125
+ if (options?.sortBy === "name") {
126
+ items.sort((a, b) => a.uri.localeCompare(b.uri));
127
+ } else if (options?.sortBy === "timestamp") {
128
+ items.sort((a, b) => {
129
+ const aTs = current.children?.get(a.uri.split("/").pop())?.value?.ts ?? 0;
130
+ const bTs = current.children?.get(b.uri.split("/").pop())?.value?.ts ?? 0;
131
+ return aTs - bTs;
132
+ });
133
+ }
134
+ if (options?.sortOrder === "desc") {
135
+ items.reverse();
136
+ }
137
+ const page = options?.page ?? 1;
138
+ const limit = options?.limit ?? 50;
139
+ const offset = (page - 1) * limit;
140
+ const paginated = items.slice(offset, offset + limit);
141
+ return Promise.resolve({
142
+ success: true,
143
+ data: paginated,
144
+ pagination: { page, limit, total: items.length }
145
+ });
146
+ }
147
+ health() {
148
+ return Promise.resolve({
149
+ status: "healthy"
150
+ });
151
+ }
152
+ getSchema() {
153
+ return Promise.resolve(Object.keys(this.schema));
154
+ }
155
+ cleanup() {
156
+ Object.keys(this.schema).forEach((program) => {
157
+ if (!this.storage.get(program)) {
158
+ this.storage.set(program, { children: /* @__PURE__ */ new Map() });
159
+ }
160
+ });
161
+ return Promise.resolve();
162
+ }
163
+ delete(uri) {
164
+ const result = target(uri, this.schema, this.storage);
165
+ if (!result.success) {
166
+ return Promise.resolve(result);
167
+ }
168
+ const { node, parts } = result;
169
+ const filteredParts = parts.filter(Boolean);
170
+ if (filteredParts.length === 0) {
171
+ return Promise.resolve({
172
+ success: false,
173
+ error: "Cannot delete root path"
174
+ });
175
+ }
176
+ let current = node;
177
+ const lastPart = filteredParts.pop();
178
+ for (const part of filteredParts) {
179
+ current = current?.children?.get(part);
180
+ if (!current) {
181
+ return Promise.resolve({
182
+ success: false,
183
+ error: `Path not found: ${part}`
184
+ });
185
+ }
186
+ }
187
+ if (!current.children?.has(lastPart)) {
188
+ return Promise.resolve({
189
+ success: false,
190
+ error: `Item not found: ${lastPart}`
191
+ });
192
+ }
193
+ current.children.delete(lastPart);
194
+ return Promise.resolve({
195
+ success: true
196
+ });
197
+ }
198
+ };
199
+
200
+ export {
201
+ MemoryClient
202
+ };