@1sat/wallet-toolbox 0.0.18 → 0.0.20

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.
@@ -5,8 +5,9 @@
5
5
  * (browser extension) and 1sat-website (React app).
6
6
  */
7
7
  import { PrivateKey } from "@bsv/sdk";
8
- import { WalletPermissionsManager, Monitor, type PermissionsManagerConfig } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
8
+ import { Monitor, type PermissionsManagerConfig, WalletPermissionsManager } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
9
9
  import { OneSatServices } from "../services/OneSatServices";
10
+ import { type FullSyncResult } from "./fullSync";
10
11
  type Chain = "main" | "test";
11
12
  /**
12
13
  * Configuration for creating a web wallet.
@@ -40,6 +41,8 @@ export interface WebWalletResult {
40
41
  monitor: Monitor;
41
42
  /** Cleanup function - stops monitor, destroys wallet */
42
43
  destroy: () => Promise<void>;
44
+ /** Full sync with remote backup (only available if remoteStorageUrl was provided and connected) */
45
+ fullSync?: () => Promise<FullSyncResult>;
43
46
  }
44
47
  /**
45
48
  * Create a web wallet with storage, services, permissions, and monitor.
@@ -5,8 +5,9 @@
5
5
  * (browser extension) and 1sat-website (React app).
6
6
  */
7
7
  import { KeyDeriver, PrivateKey } from "@bsv/sdk";
8
- import { Wallet, WalletStorageManager, StorageProvider, StorageIdb, StorageClient, WalletPermissionsManager, Services, Monitor, } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
8
+ import { Monitor, Services, StorageClient, StorageIdb, StorageProvider, Wallet, WalletPermissionsManager, WalletStorageManager, } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
9
9
  import { OneSatServices } from "../services/OneSatServices";
10
+ import { fullSync } from "./fullSync";
10
11
  // Default database name for IndexedDB storage
11
12
  const DEFAULT_DATABASE_NAME = "wallet";
12
13
  // Default timeout for remote storage connection
@@ -74,6 +75,8 @@ export async function createWebWallet(config) {
74
75
  // 4. Create storage manager (local-only initially)
75
76
  let storage = new WalletStorageManager(identityPubKey, localStorage);
76
77
  await storage.makeAvailable();
78
+ // Track remote client for fullSync
79
+ let remoteClient;
77
80
  // 5. Create the underlying Wallet
78
81
  const underlyingWallet = new Wallet({
79
82
  chain,
@@ -85,7 +88,7 @@ export async function createWebWallet(config) {
85
88
  if (config.remoteStorageUrl) {
86
89
  console.log(`[createWebWallet] Attempting remote storage connection to ${config.remoteStorageUrl}`);
87
90
  try {
88
- const remoteClient = new StorageClient(underlyingWallet, config.remoteStorageUrl);
91
+ remoteClient = new StorageClient(underlyingWallet, config.remoteStorageUrl);
89
92
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Remote storage connection timeout")), DEFAULT_REMOTE_STORAGE_TIMEOUT));
90
93
  await Promise.race([remoteClient.makeAvailable(), timeoutPromise]);
91
94
  // Remote connected - recreate storage manager with backup
@@ -97,36 +100,44 @@ export async function createWebWallet(config) {
97
100
  const storageAny = storage;
98
101
  console.log("[createWebWallet] Storage state:", {
99
102
  activeKey: storageAny._active?.settings?.storageIdentityKey,
100
- backups: storageAny._backups?.map(b => b.settings?.storageIdentityKey),
101
- conflictingActives: storageAny._conflictingActives?.map(c => c.settings?.storageIdentityKey),
103
+ backups: storageAny._backups?.map((b) => b.settings?.storageIdentityKey),
104
+ conflictingActives: storageAny._conflictingActives?.map((c) => c.settings?.storageIdentityKey),
102
105
  });
103
- // Treat backups as conflicts to pull any data they have.
104
- // Remote storage may have transactions that local doesn't know about
105
- // (e.g., from sweep-ui syncing to remote).
106
- if (storageAny._backups && storageAny._backups.length > 0) {
107
- console.log("[createWebWallet] Reclassifying backups as conflicts to pull remote data...");
108
- storageAny._conflictingActives = storageAny._conflictingActives || [];
109
- storageAny._conflictingActives.push(...storageAny._backups);
110
- storageAny._backups = [];
111
- }
112
- // If there are conflicting actives (including reclassified backups), resolve by merging into local
113
- // This is now blocking since setActive no longer holds IDB transactions during network calls
114
- if (storageAny._conflictingActives && storageAny._conflictingActives.length > 0) {
106
+ // Only resolve actual conflicts, don't treat backups as conflicts
107
+ if (storageAny._conflictingActives &&
108
+ storageAny._conflictingActives.length > 0) {
115
109
  const localKey = storageAny._active?.settings?.storageIdentityKey;
116
110
  if (localKey && storageAny.setActive) {
117
- console.log("[createWebWallet] Syncing with remote storage...");
111
+ console.log("[createWebWallet] Resolving conflicting actives...");
118
112
  try {
119
113
  await storageAny.setActive(localKey, (msg) => {
120
- console.log("[createWebWallet] Sync:", msg);
114
+ console.log("[createWebWallet] Conflict resolution:", msg);
121
115
  return msg;
122
116
  });
123
- console.log("[createWebWallet] Remote sync complete");
117
+ console.log("[createWebWallet] Conflict resolution complete");
124
118
  }
125
119
  catch (err) {
126
- console.log("[createWebWallet] Remote sync failed:", err instanceof Error ? err.message : err);
120
+ console.log("[createWebWallet] Conflict resolution failed:", err instanceof Error ? err.message : err);
127
121
  }
128
122
  }
129
123
  }
124
+ else if (storageAny._backups &&
125
+ storageAny._backups.length > 0 &&
126
+ storageAny.updateBackups) {
127
+ // No conflicts - push local state to remote backup (fire-and-forget)
128
+ console.log("[createWebWallet] Pushing local state to remote backup...");
129
+ storageAny
130
+ .updateBackups(undefined, (msg) => {
131
+ console.log("[createWebWallet] Backup:", msg);
132
+ return msg;
133
+ })
134
+ .then(() => {
135
+ console.log("[createWebWallet] Backup complete");
136
+ })
137
+ .catch((err) => {
138
+ console.log("[createWebWallet] Backup failed:", err instanceof Error ? err.message : err);
139
+ });
140
+ }
130
141
  // Update wallet's storage reference
131
142
  underlyingWallet._storage = storage;
132
143
  console.log("[createWebWallet] Remote storage connected successfully");
@@ -157,10 +168,21 @@ export async function createWebWallet(config) {
157
168
  await monitor.destroy();
158
169
  await underlyingWallet.destroy();
159
170
  };
171
+ // 10. Create fullSync function if remote storage is connected
172
+ const fullSyncFn = remoteClient
173
+ ? async () => {
174
+ return fullSync({
175
+ storage,
176
+ remoteStorage: remoteClient,
177
+ identityKey: identityPubKey,
178
+ });
179
+ }
180
+ : undefined;
160
181
  return {
161
182
  wallet,
162
183
  services: oneSatServices,
163
184
  monitor,
164
185
  destroy,
186
+ fullSync: fullSyncFn,
165
187
  };
166
188
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Full wallet synchronization with remote backup server.
3
+ *
4
+ * Performs a complete resync: push local → reset sync state → full pull from server.
5
+ * This is a deliberate user action (not automatic) for recovering from sync issues.
6
+ */
7
+ import type { WalletStorageManager } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
8
+ import type { sdk as mobileToolboxSdk } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
9
+ type WalletStorageProvider = mobileToolboxSdk.WalletStorageProvider;
10
+ export type FullSyncStage = "pushing" | "resetting" | "pulling" | "complete";
11
+ export interface FullSyncOptions {
12
+ /** Local storage manager */
13
+ storage: WalletStorageManager;
14
+ /** Remote backup storage provider (StorageClient) */
15
+ remoteStorage: WalletStorageProvider;
16
+ /** Identity key for the wallet */
17
+ identityKey: string;
18
+ /** Optional progress callback */
19
+ onProgress?: (stage: FullSyncStage, message: string) => void;
20
+ }
21
+ export interface FullSyncResult {
22
+ pushed: {
23
+ inserts: number;
24
+ updates: number;
25
+ };
26
+ pulled: {
27
+ inserts: number;
28
+ updates: number;
29
+ };
30
+ }
31
+ /**
32
+ * Perform a full sync with the remote backup server.
33
+ *
34
+ * Steps:
35
+ * 1. Push all local data to remote backup
36
+ * 2. Clear local syncMap / reset sync state
37
+ * 3. Pull ALL data from server (not incremental)
38
+ * 4. Rebuild complete server→client ID mappings
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const result = await fullSync({
43
+ * storage,
44
+ * remoteStorage: remoteClient,
45
+ * identityKey: pubKey,
46
+ * onProgress: (stage, msg) => console.log(`[${stage}] ${msg}`)
47
+ * });
48
+ * console.log(`Pushed: ${result.pushed.inserts}/${result.pushed.updates}`);
49
+ * console.log(`Pulled: ${result.pulled.inserts}/${result.pulled.updates}`);
50
+ * ```
51
+ */
52
+ export declare function fullSync(options: FullSyncOptions): Promise<FullSyncResult>;
53
+ export {};
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Full wallet synchronization with remote backup server.
3
+ *
4
+ * Performs a complete resync: push local → reset sync state → full pull from server.
5
+ * This is a deliberate user action (not automatic) for recovering from sync issues.
6
+ */
7
+ import { createSyncMap } from "@bsv/wallet-toolbox-mobile/out/src/storage/schema/entities/EntityBase.js";
8
+ /**
9
+ * Perform a full sync with the remote backup server.
10
+ *
11
+ * Steps:
12
+ * 1. Push all local data to remote backup
13
+ * 2. Clear local syncMap / reset sync state
14
+ * 3. Pull ALL data from server (not incremental)
15
+ * 4. Rebuild complete server→client ID mappings
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const result = await fullSync({
20
+ * storage,
21
+ * remoteStorage: remoteClient,
22
+ * identityKey: pubKey,
23
+ * onProgress: (stage, msg) => console.log(`[${stage}] ${msg}`)
24
+ * });
25
+ * console.log(`Pushed: ${result.pushed.inserts}/${result.pushed.updates}`);
26
+ * console.log(`Pulled: ${result.pulled.inserts}/${result.pulled.updates}`);
27
+ * ```
28
+ */
29
+ export async function fullSync(options) {
30
+ const { storage, remoteStorage, identityKey, onProgress } = options;
31
+ // Step 1: Push local data to remote
32
+ onProgress?.("pushing", "Pushing local data to remote...");
33
+ const auth = await storage.getAuth();
34
+ const pushResult = await storage.syncToWriter(auth, remoteStorage);
35
+ onProgress?.("pushing", `Pushed ${pushResult.inserts} inserts, ${pushResult.updates} updates`);
36
+ // Step 2: Reset sync state to force full pull
37
+ onProgress?.("resetting", "Resetting sync state...");
38
+ const remoteSettings = remoteStorage.getSettings();
39
+ await storage.runAsStorageProvider(async (active) => {
40
+ const syncStates = await active.findSyncStates({
41
+ partial: {
42
+ userId: auth.userId,
43
+ storageIdentityKey: remoteSettings.storageIdentityKey,
44
+ },
45
+ });
46
+ if (syncStates.length > 0) {
47
+ const syncState = syncStates[0];
48
+ await active.updateSyncState(syncState.syncStateId, {
49
+ syncMap: JSON.stringify(createSyncMap()),
50
+ when: undefined,
51
+ status: "unknown",
52
+ });
53
+ onProgress?.("resetting", "Sync state reset complete");
54
+ }
55
+ else {
56
+ onProgress?.("resetting", "No existing sync state found");
57
+ }
58
+ });
59
+ // Step 3: Pull from remote (full pull due to reset state)
60
+ onProgress?.("pulling", "Pulling all data from remote...");
61
+ const pullResult = await storage.syncFromReader(identityKey, remoteStorage);
62
+ onProgress?.("pulling", `Pulled ${pullResult.inserts} inserts, ${pullResult.updates} updates`);
63
+ // Step 4: Complete
64
+ onProgress?.("complete", "Full sync complete");
65
+ return {
66
+ pushed: { inserts: pushResult.inserts, updates: pushResult.updates },
67
+ pulled: { inserts: pullResult.inserts, updates: pullResult.updates },
68
+ };
69
+ }
@@ -1 +1,2 @@
1
1
  export * from "./factory";
2
+ export * from "./fullSync";
@@ -1 +1,2 @@
1
1
  export * from "./factory";
2
+ export * from "./fullSync";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1sat/wallet-toolbox",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "BSV wallet library extending @bsv/wallet-toolbox with 1Sat Ordinals protocol support",
5
5
  "author": "1Sat Team",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@
42
42
  "buffer": "^6.0.3"
43
43
  },
44
44
  "peerDependencies": {
45
- "@bsv/wallet-toolbox-mobile": "npm:@bopen-io/wallet-toolbox-mobile@^1.7.20-idb-fix.11"
45
+ "@bsv/wallet-toolbox-mobile": "npm:@bopen-io/wallet-toolbox-mobile@^1.7.20-idb-fix.15"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@bsv/wallet-toolbox-mobile": {
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@biomejs/biome": "^1.9.4",
54
- "@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@^1.7.20-idb-fix.11",
55
- "@bsv/wallet-toolbox-mobile": "npm:@bopen-io/wallet-toolbox-mobile@^1.7.20-idb-fix.11",
54
+ "@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@^1.7.20-idb-fix.15",
55
+ "@bsv/wallet-toolbox-mobile": "npm:@bopen-io/wallet-toolbox-mobile@^1.7.20-idb-fix.15",
56
56
  "@types/bun": "^1.3.4",
57
57
  "@types/chrome": "^0.1.32",
58
58
  "typescript": "^5.9.3"