database-devtools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/dist/adapter/index.cjs +314 -0
- package/dist/adapter/index.cjs.map +1 -0
- package/dist/adapter/index.d.cts +34 -0
- package/dist/adapter/index.d.ts +34 -0
- package/dist/adapter/index.js +6 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/adapter-BjJm71wW.d.cts +30 -0
- package/dist/adapter-DJSjDewg.d.ts +30 -0
- package/dist/adapters/sqlite/index.cjs +251 -0
- package/dist/adapters/sqlite/index.cjs.map +1 -0
- package/dist/adapters/sqlite/index.d.cts +21 -0
- package/dist/adapters/sqlite/index.d.ts +21 -0
- package/dist/adapters/sqlite/index.js +6 -0
- package/dist/adapters/sqlite/index.js.map +1 -0
- package/dist/chunk-2JGL2JVD.js +52 -0
- package/dist/chunk-2JGL2JVD.js.map +1 -0
- package/dist/chunk-AZECU6D7.js +27 -0
- package/dist/chunk-AZECU6D7.js.map +1 -0
- package/dist/chunk-BLOSGEQV.js +71 -0
- package/dist/chunk-BLOSGEQV.js.map +1 -0
- package/dist/chunk-CPVVIH5G.js +258 -0
- package/dist/chunk-CPVVIH5G.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +10 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-HIDZTOJ3.js +9 -0
- package/dist/chunk-HIDZTOJ3.js.map +1 -0
- package/dist/chunk-I65IM47D.js +243 -0
- package/dist/chunk-I65IM47D.js.map +1 -0
- package/dist/chunk-MXVIGMVT.js +3 -0
- package/dist/chunk-MXVIGMVT.js.map +1 -0
- package/dist/chunk-O6AO7KHA.js +3 -0
- package/dist/chunk-O6AO7KHA.js.map +1 -0
- package/dist/chunk-TNDOAHT5.js +1784 -0
- package/dist/chunk-TNDOAHT5.js.map +1 -0
- package/dist/chunk-ZS5MZR75.js +655 -0
- package/dist/chunk-ZS5MZR75.js.map +1 -0
- package/dist/cli.cjs +2053 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/client/index.cjs +947 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +75 -0
- package/dist/client/index.d.ts +75 -0
- package/dist/client/index.js +98 -0
- package/dist/client/index.js.map +1 -0
- package/dist/createDevToolsServer-B7NQ_5H4.d.cts +292 -0
- package/dist/createDevToolsServer-DlNMoCJR.d.ts +292 -0
- package/dist/handleDeviceSnapshot-CR-4QMdk.d.ts +116 -0
- package/dist/handleDeviceSnapshot-cYGuitSc.d.cts +116 -0
- package/dist/index.cjs +5877 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +2748 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector/index.cjs +57 -0
- package/dist/inspector/index.cjs.map +1 -0
- package/dist/inspector/index.d.cts +26 -0
- package/dist/inspector/index.d.ts +26 -0
- package/dist/inspector/index.js +5 -0
- package/dist/inspector/index.js.map +1 -0
- package/dist/inspectors/sqlite/index.cjs +306 -0
- package/dist/inspectors/sqlite/index.cjs.map +1 -0
- package/dist/inspectors/sqlite/index.d.cts +53 -0
- package/dist/inspectors/sqlite/index.d.ts +53 -0
- package/dist/inspectors/sqlite/index.js +260 -0
- package/dist/inspectors/sqlite/index.js.map +1 -0
- package/dist/register-BuicFu9L.d.cts +32 -0
- package/dist/register-aITLN9Q9.d.ts +32 -0
- package/dist/server/index.cjs +1948 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +15 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +7 -0
- package/dist/server/index.js.map +1 -0
- package/dist/snapshot-Dhyecgo2.d.cts +33 -0
- package/dist/snapshot-Dhyecgo2.d.ts +33 -0
- package/dist/types/protocol.cjs +309 -0
- package/dist/types/protocol.cjs.map +1 -0
- package/dist/types/protocol.d.cts +291 -0
- package/dist/types/protocol.d.ts +291 -0
- package/dist/types/protocol.js +4 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/types-BRLJdBRY.d.cts +16 -0
- package/dist/types-CF6qEU-_.d.ts +16 -0
- package/dist/types-CIA11diJ.d.cts +62 -0
- package/dist/types-CKVRCX-e.d.ts +62 -0
- package/dist/web/assets/index-CLEaIsUd.css +1 -0
- package/dist/web/assets/index-DQrPRTOy.js +100 -0
- package/dist/web/assets/sql-wasm-UFUCzYNW.wasm +0 -0
- package/dist/web/index.html +26 -0
- package/dist/write-C3toEIMs.d.cts +21 -0
- package/dist/write-C3toEIMs.d.ts +21 -0
- package/package.json +137 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import { generateDeviceId, generateTransactionId, generateWriteId } from './chunk-AZECU6D7.js';
|
|
2
|
+
import { DevToolsRole, buildDevToolsWsUrl, DEFAULT_DEVTOOLS_PORT, PROJECT_DEVICE_EXPORTS_API_PATH, PROJECT_DATABASE_API_PATH, PROJECT_DATABASE_META_API_PATH, PROJECT_DATABASES_API_PATH, wsUrlToHttpUrl, buildDeviceSnapshotUrl, MessageType, isPingMessage, isDeviceStatusMessage, isSnapshotUploadRequestedMessage, isExportSnapshotErrorMessage, isRefreshStatusMessage, isSnapshotReadyMessage, isRefreshErrorMessage, isBeginTransactionMessage, isCommitTransactionMessage, isRollbackTransactionMessage, isExecuteWriteMessage, isTransactionStatusMessage, isWriteResultMessage, isWriteErrorMessage } from './chunk-CPVVIH5G.js';
|
|
3
|
+
import { SNAPSHOT_MIME_HEADER, SNAPSHOT_KIND_HEADER, SNAPSHOT_NAME_HEADER } from './chunk-HIDZTOJ3.js';
|
|
4
|
+
|
|
5
|
+
// src/utils/reconnect.ts
|
|
6
|
+
var DEFAULT_BASE_MS = 1e3;
|
|
7
|
+
var DEFAULT_MAX_MS = 3e4;
|
|
8
|
+
var DEFAULT_JITTER_MS = 500;
|
|
9
|
+
function calculateBackoffDelay(attempt, options = {}) {
|
|
10
|
+
const baseMs = options.baseMs ?? DEFAULT_BASE_MS;
|
|
11
|
+
const maxMs = options.maxMs ?? DEFAULT_MAX_MS;
|
|
12
|
+
const jitterMs = options.jitterMs ?? DEFAULT_JITTER_MS;
|
|
13
|
+
const exponential = baseMs * 2 ** attempt;
|
|
14
|
+
const capped = Math.min(exponential, maxMs);
|
|
15
|
+
const jitter = Math.floor(Math.random() * jitterMs);
|
|
16
|
+
return capped + jitter;
|
|
17
|
+
}
|
|
18
|
+
var ReconnectScheduler = class {
|
|
19
|
+
attempt = 0;
|
|
20
|
+
timer = null;
|
|
21
|
+
onReconnect;
|
|
22
|
+
options;
|
|
23
|
+
constructor({ onReconnect, ...options }) {
|
|
24
|
+
this.onReconnect = onReconnect;
|
|
25
|
+
this.options = options;
|
|
26
|
+
}
|
|
27
|
+
schedule() {
|
|
28
|
+
if (this.timer !== null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const delay = calculateBackoffDelay(this.attempt, this.options);
|
|
32
|
+
this.attempt += 1;
|
|
33
|
+
this.timer = setTimeout(() => {
|
|
34
|
+
this.timer = null;
|
|
35
|
+
this.onReconnect();
|
|
36
|
+
}, delay);
|
|
37
|
+
}
|
|
38
|
+
reset() {
|
|
39
|
+
this.attempt = 0;
|
|
40
|
+
this.cancel();
|
|
41
|
+
}
|
|
42
|
+
cancel() {
|
|
43
|
+
if (this.timer !== null) {
|
|
44
|
+
clearTimeout(this.timer);
|
|
45
|
+
this.timer = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/client/createDevToolsClient.ts
|
|
51
|
+
var WRITE_TIMEOUT_MS = 3e4;
|
|
52
|
+
function createDevToolsClient(options = {}) {
|
|
53
|
+
const role = options.role ?? DevToolsRole.MOBILE;
|
|
54
|
+
const WebSocketImpl = options.WebSocketImpl ?? globalThis.WebSocket;
|
|
55
|
+
if (!WebSocketImpl) {
|
|
56
|
+
throw new Error("WebSocket is not available in this environment.");
|
|
57
|
+
}
|
|
58
|
+
let socket = null;
|
|
59
|
+
let deviceStatus = null;
|
|
60
|
+
let connectionState = "disconnected";
|
|
61
|
+
let lastConnectionError = null;
|
|
62
|
+
let intentionalDisconnect = false;
|
|
63
|
+
let transactionState = "idle";
|
|
64
|
+
let activeTransactionId = null;
|
|
65
|
+
let activeDeviceId = null;
|
|
66
|
+
const pendingTransaction = /* @__PURE__ */ new Map();
|
|
67
|
+
const pendingWrites = /* @__PURE__ */ new Map();
|
|
68
|
+
const deviceId = role === DevToolsRole.MOBILE ? options.deviceId ?? generateDeviceId() : void 0;
|
|
69
|
+
let wsUrl = options.serverUrl ?? buildDevToolsWsUrl("localhost", DEFAULT_DEVTOOLS_PORT);
|
|
70
|
+
const setConnectionState = (state) => {
|
|
71
|
+
if (connectionState === state) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
connectionState = state;
|
|
75
|
+
options.onConnectionStateChange?.(state);
|
|
76
|
+
};
|
|
77
|
+
const setTransactionState = (state) => {
|
|
78
|
+
if (transactionState === state) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
transactionState = state;
|
|
82
|
+
options.onTransactionStateChange?.(state);
|
|
83
|
+
};
|
|
84
|
+
const resetTransaction = () => {
|
|
85
|
+
setTransactionState("idle");
|
|
86
|
+
activeTransactionId = null;
|
|
87
|
+
activeDeviceId = null;
|
|
88
|
+
};
|
|
89
|
+
const rejectPendingTransaction = (transactionId, error) => {
|
|
90
|
+
const pending = pendingTransaction.get(transactionId);
|
|
91
|
+
if (pending) {
|
|
92
|
+
pending.reject(error);
|
|
93
|
+
pendingTransaction.delete(transactionId);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const rejectAllPending = (error) => {
|
|
97
|
+
for (const [id, pending] of pendingTransaction) {
|
|
98
|
+
pending.reject(error);
|
|
99
|
+
pendingTransaction.delete(id);
|
|
100
|
+
}
|
|
101
|
+
for (const [id, pending] of pendingWrites) {
|
|
102
|
+
pending.reject(error);
|
|
103
|
+
pendingWrites.delete(id);
|
|
104
|
+
}
|
|
105
|
+
resetTransaction();
|
|
106
|
+
};
|
|
107
|
+
const reconnectScheduler = new ReconnectScheduler({
|
|
108
|
+
onReconnect: () => {
|
|
109
|
+
openSocket();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const send = (message) => {
|
|
113
|
+
if (!socket || socket.readyState !== WebSocketImpl.OPEN) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
socket.send(JSON.stringify({ ...message, timestamp: Date.now() }));
|
|
117
|
+
};
|
|
118
|
+
const sendRegister = () => {
|
|
119
|
+
send({
|
|
120
|
+
type: MessageType.REGISTER,
|
|
121
|
+
role,
|
|
122
|
+
...deviceId ? { deviceId } : {},
|
|
123
|
+
...options.metadata ? { metadata: options.metadata } : {}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const handleServerMessage = (parsed) => {
|
|
127
|
+
if (isPingMessage(parsed)) {
|
|
128
|
+
send({
|
|
129
|
+
type: MessageType.PONG,
|
|
130
|
+
pingId: parsed.pingId
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (isDeviceStatusMessage(parsed)) {
|
|
135
|
+
deviceStatus = parsed.payload;
|
|
136
|
+
options.onDeviceStatus?.(parsed.payload);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (isSnapshotUploadRequestedMessage(parsed)) {
|
|
140
|
+
options.onSnapshotUploadRequested?.(parsed);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (isExportSnapshotErrorMessage(parsed)) {
|
|
144
|
+
options.onExportSnapshotError?.(parsed);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (isRefreshStatusMessage(parsed)) {
|
|
148
|
+
options.onRefreshStatus?.(parsed);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (isSnapshotReadyMessage(parsed)) {
|
|
152
|
+
options.onSnapshotReady?.(parsed);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (isRefreshErrorMessage(parsed)) {
|
|
156
|
+
options.onRefreshError?.(parsed);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (isBeginTransactionMessage(parsed)) {
|
|
160
|
+
options.onBeginTransaction?.(parsed);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (isCommitTransactionMessage(parsed)) {
|
|
164
|
+
options.onCommitTransaction?.(parsed);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (isRollbackTransactionMessage(parsed)) {
|
|
168
|
+
options.onRollbackTransaction?.(parsed);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (isExecuteWriteMessage(parsed)) {
|
|
172
|
+
options.onExecuteWrite?.(parsed);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (isTransactionStatusMessage(parsed)) {
|
|
176
|
+
options.onTransactionStatus?.(parsed);
|
|
177
|
+
if (role !== DevToolsRole.BROWSER) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const pending = pendingTransaction.get(parsed.transactionId);
|
|
181
|
+
if (parsed.state === "open" && pending?.action === "begin") {
|
|
182
|
+
setTransactionState("open");
|
|
183
|
+
activeTransactionId = parsed.transactionId;
|
|
184
|
+
activeDeviceId = parsed.deviceId;
|
|
185
|
+
pending.resolve();
|
|
186
|
+
pendingTransaction.delete(parsed.transactionId);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (parsed.state === "idle" && pending && (pending.action === "commit" || pending.action === "rollback")) {
|
|
190
|
+
resetTransaction();
|
|
191
|
+
pending.resolve();
|
|
192
|
+
pendingTransaction.delete(parsed.transactionId);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (parsed.state === "failed") {
|
|
196
|
+
rejectPendingTransaction(
|
|
197
|
+
parsed.transactionId,
|
|
198
|
+
new Error(parsed.message ?? "Transaction failed")
|
|
199
|
+
);
|
|
200
|
+
resetTransaction();
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (isWriteResultMessage(parsed)) {
|
|
205
|
+
options.onWriteResult?.(parsed);
|
|
206
|
+
const pending = pendingWrites.get(parsed.writeId);
|
|
207
|
+
if (pending) {
|
|
208
|
+
pending.resolve({ rowsAffected: parsed.rowsAffected });
|
|
209
|
+
pendingWrites.delete(parsed.writeId);
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (isWriteErrorMessage(parsed)) {
|
|
214
|
+
options.onWriteError?.(parsed);
|
|
215
|
+
const error = new Error(parsed.message);
|
|
216
|
+
if (parsed.writeId) {
|
|
217
|
+
const pendingWrite = pendingWrites.get(parsed.writeId);
|
|
218
|
+
if (pendingWrite) {
|
|
219
|
+
pendingWrite.reject(error);
|
|
220
|
+
pendingWrites.delete(parsed.writeId);
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
rejectPendingTransaction(parsed.transactionId, error);
|
|
224
|
+
resetTransaction();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const openSocket = () => {
|
|
229
|
+
if (socket && (socket.readyState === WebSocketImpl.OPEN || socket.readyState === WebSocketImpl.CONNECTING)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
setConnectionState(intentionalDisconnect ? "disconnected" : connectionState === "disconnected" ? "connecting" : "reconnecting");
|
|
233
|
+
socket = new WebSocketImpl(wsUrl);
|
|
234
|
+
socket.addEventListener("open", () => {
|
|
235
|
+
reconnectScheduler.reset();
|
|
236
|
+
lastConnectionError = null;
|
|
237
|
+
setConnectionState("connected");
|
|
238
|
+
options.onConnect?.();
|
|
239
|
+
sendRegister();
|
|
240
|
+
});
|
|
241
|
+
socket.addEventListener("message", (event) => {
|
|
242
|
+
let parsed;
|
|
243
|
+
try {
|
|
244
|
+
parsed = JSON.parse(String(event.data));
|
|
245
|
+
} catch {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
handleServerMessage(parsed);
|
|
249
|
+
});
|
|
250
|
+
socket.addEventListener("close", () => {
|
|
251
|
+
socket = null;
|
|
252
|
+
options.onDisconnect?.();
|
|
253
|
+
rejectAllPending(new Error("Disconnected from DevTools hub"));
|
|
254
|
+
if (intentionalDisconnect) {
|
|
255
|
+
setConnectionState("disconnected");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
setConnectionState("reconnecting");
|
|
259
|
+
reconnectScheduler.schedule();
|
|
260
|
+
});
|
|
261
|
+
socket.addEventListener("error", () => {
|
|
262
|
+
const error = new Error(`WebSocket connection failed: ${wsUrl}`);
|
|
263
|
+
lastConnectionError = error.message;
|
|
264
|
+
options.onError?.(error);
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
const connect = () => {
|
|
268
|
+
intentionalDisconnect = false;
|
|
269
|
+
openSocket();
|
|
270
|
+
};
|
|
271
|
+
const disconnect = () => {
|
|
272
|
+
intentionalDisconnect = true;
|
|
273
|
+
reconnectScheduler.cancel();
|
|
274
|
+
socket?.close();
|
|
275
|
+
socket = null;
|
|
276
|
+
rejectAllPending(new Error("Disconnected"));
|
|
277
|
+
setConnectionState("disconnected");
|
|
278
|
+
};
|
|
279
|
+
const setServerUrl = (url) => {
|
|
280
|
+
wsUrl = url;
|
|
281
|
+
const wasConnected = connectionState === "connected" || connectionState === "connecting" || connectionState === "reconnecting";
|
|
282
|
+
disconnect();
|
|
283
|
+
intentionalDisconnect = false;
|
|
284
|
+
if (wasConnected) {
|
|
285
|
+
connect();
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
const requestRefresh = (targetDeviceId) => {
|
|
289
|
+
send({
|
|
290
|
+
type: MessageType.REFRESH_REQUEST,
|
|
291
|
+
deviceId: targetDeviceId,
|
|
292
|
+
refreshType: "snapshot"
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const requestExportSnapshot = () => {
|
|
296
|
+
send({
|
|
297
|
+
type: MessageType.EXPORT_SNAPSHOT_REQUEST,
|
|
298
|
+
refreshType: "snapshot"
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
const sendTransactionAck = (message) => {
|
|
302
|
+
send({
|
|
303
|
+
type: MessageType.TRANSACTION_ACK,
|
|
304
|
+
...message
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const sendWriteAck = (message) => {
|
|
308
|
+
send({
|
|
309
|
+
type: MessageType.WRITE_ACK,
|
|
310
|
+
...message
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
const waitForTransaction = (transactionId, action) => {
|
|
314
|
+
return new Promise((resolve, reject) => {
|
|
315
|
+
const timer = setTimeout(() => {
|
|
316
|
+
pendingTransaction.delete(transactionId);
|
|
317
|
+
reject(new Error(`Transaction ${action} timed out`));
|
|
318
|
+
}, WRITE_TIMEOUT_MS);
|
|
319
|
+
pendingTransaction.set(transactionId, {
|
|
320
|
+
action,
|
|
321
|
+
resolve: () => {
|
|
322
|
+
clearTimeout(timer);
|
|
323
|
+
resolve();
|
|
324
|
+
},
|
|
325
|
+
reject: (error) => {
|
|
326
|
+
clearTimeout(timer);
|
|
327
|
+
reject(error);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
const beginTransaction = async (targetDeviceId) => {
|
|
333
|
+
if (role !== DevToolsRole.BROWSER) {
|
|
334
|
+
throw new Error("beginTransaction is only available for browser clients");
|
|
335
|
+
}
|
|
336
|
+
if (transactionState !== "idle") {
|
|
337
|
+
throw new Error("A transaction is already active");
|
|
338
|
+
}
|
|
339
|
+
const transactionId = generateTransactionId();
|
|
340
|
+
setTransactionState("opening");
|
|
341
|
+
activeTransactionId = transactionId;
|
|
342
|
+
activeDeviceId = targetDeviceId;
|
|
343
|
+
const promise = waitForTransaction(transactionId, "begin");
|
|
344
|
+
send({
|
|
345
|
+
type: MessageType.BEGIN_TRANSACTION_REQUEST,
|
|
346
|
+
transactionId,
|
|
347
|
+
deviceId: targetDeviceId
|
|
348
|
+
});
|
|
349
|
+
await promise;
|
|
350
|
+
};
|
|
351
|
+
const commitTransaction = async () => {
|
|
352
|
+
if (role !== DevToolsRole.BROWSER) {
|
|
353
|
+
throw new Error("commitTransaction is only available for browser clients");
|
|
354
|
+
}
|
|
355
|
+
if (!activeTransactionId || !activeDeviceId || transactionState !== "open") {
|
|
356
|
+
throw new Error("No open transaction");
|
|
357
|
+
}
|
|
358
|
+
const transactionId = activeTransactionId;
|
|
359
|
+
const deviceIdForCommit = activeDeviceId;
|
|
360
|
+
setTransactionState("committing");
|
|
361
|
+
const promise = waitForTransaction(transactionId, "commit");
|
|
362
|
+
send({
|
|
363
|
+
type: MessageType.COMMIT_TRANSACTION_REQUEST,
|
|
364
|
+
transactionId,
|
|
365
|
+
deviceId: deviceIdForCommit
|
|
366
|
+
});
|
|
367
|
+
await promise;
|
|
368
|
+
};
|
|
369
|
+
const rollbackTransaction = async () => {
|
|
370
|
+
if (role !== DevToolsRole.BROWSER) {
|
|
371
|
+
throw new Error("rollbackTransaction is only available for browser clients");
|
|
372
|
+
}
|
|
373
|
+
if (!activeTransactionId || !activeDeviceId) {
|
|
374
|
+
throw new Error("No active transaction");
|
|
375
|
+
}
|
|
376
|
+
const transactionId = activeTransactionId;
|
|
377
|
+
const deviceIdForRollback = activeDeviceId;
|
|
378
|
+
setTransactionState("rolling_back");
|
|
379
|
+
const promise = waitForTransaction(transactionId, "rollback");
|
|
380
|
+
send({
|
|
381
|
+
type: MessageType.ROLLBACK_TRANSACTION_REQUEST,
|
|
382
|
+
transactionId,
|
|
383
|
+
deviceId: deviceIdForRollback
|
|
384
|
+
});
|
|
385
|
+
await promise;
|
|
386
|
+
};
|
|
387
|
+
const executeWrite = async (operation) => {
|
|
388
|
+
if (role !== DevToolsRole.BROWSER) {
|
|
389
|
+
throw new Error("executeWrite is only available for browser clients");
|
|
390
|
+
}
|
|
391
|
+
if (!activeTransactionId || !activeDeviceId || transactionState !== "open") {
|
|
392
|
+
throw new Error("No open transaction");
|
|
393
|
+
}
|
|
394
|
+
const writeId = generateWriteId();
|
|
395
|
+
const promise = new Promise((resolve, reject) => {
|
|
396
|
+
const timer = setTimeout(() => {
|
|
397
|
+
pendingWrites.delete(writeId);
|
|
398
|
+
reject(new Error("Write operation timed out"));
|
|
399
|
+
}, WRITE_TIMEOUT_MS);
|
|
400
|
+
pendingWrites.set(writeId, {
|
|
401
|
+
resolve: (result) => {
|
|
402
|
+
clearTimeout(timer);
|
|
403
|
+
resolve(result);
|
|
404
|
+
},
|
|
405
|
+
reject: (error) => {
|
|
406
|
+
clearTimeout(timer);
|
|
407
|
+
reject(error);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
send({
|
|
412
|
+
type: MessageType.WRITE_REQUEST,
|
|
413
|
+
writeId,
|
|
414
|
+
transactionId: activeTransactionId,
|
|
415
|
+
deviceId: activeDeviceId,
|
|
416
|
+
operation
|
|
417
|
+
});
|
|
418
|
+
return promise;
|
|
419
|
+
};
|
|
420
|
+
return {
|
|
421
|
+
connect,
|
|
422
|
+
disconnect,
|
|
423
|
+
getConnectionState: () => connectionState,
|
|
424
|
+
getLastConnectionError: () => lastConnectionError,
|
|
425
|
+
getDeviceStatus: () => deviceStatus,
|
|
426
|
+
getDeviceId: () => deviceId,
|
|
427
|
+
getServerUrl: () => wsUrl,
|
|
428
|
+
setServerUrl,
|
|
429
|
+
send,
|
|
430
|
+
requestRefresh,
|
|
431
|
+
requestExportSnapshot,
|
|
432
|
+
sendTransactionAck,
|
|
433
|
+
sendWriteAck,
|
|
434
|
+
beginTransaction,
|
|
435
|
+
commitTransaction,
|
|
436
|
+
rollbackTransaction,
|
|
437
|
+
executeWrite,
|
|
438
|
+
getTransactionState: () => transactionState,
|
|
439
|
+
getActiveTransactionId: () => activeTransactionId
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/client/fetchSnapshot.ts
|
|
444
|
+
async function fetchSnapshot(downloadUrl) {
|
|
445
|
+
let response;
|
|
446
|
+
try {
|
|
447
|
+
response = await fetch(downloadUrl);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
const networkMessage = error instanceof Error ? error.message : String(error);
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Network error fetching snapshot from ${downloadUrl}. ${networkMessage} If the web UI runs on a different port than the hub, ensure CORS is enabled on the hub HTTP API.`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
if (!response.ok) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`Failed to fetch snapshot from ${downloadUrl}: ${response.status} ${response.statusText}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
return response.arrayBuffer();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/client/resolveDeviceDatabaseUrl.ts
|
|
463
|
+
function resolveApiBase(serverUrl, options) {
|
|
464
|
+
if (options?.useSameOriginApi && typeof window !== "undefined") {
|
|
465
|
+
return window.location.origin;
|
|
466
|
+
}
|
|
467
|
+
if (serverUrl.startsWith("ws://") || serverUrl.startsWith("wss://")) {
|
|
468
|
+
return wsUrlToHttpUrl(serverUrl);
|
|
469
|
+
}
|
|
470
|
+
return serverUrl.replace(/\/$/, "");
|
|
471
|
+
}
|
|
472
|
+
function resolveDeviceExportDatabaseUrl(serverUrl, deviceId, options) {
|
|
473
|
+
const base = resolveApiBase(serverUrl, options);
|
|
474
|
+
return `${base}${PROJECT_DEVICE_EXPORTS_API_PATH}/${encodeURIComponent(deviceId)}/database`;
|
|
475
|
+
}
|
|
476
|
+
function resolveDeviceExportDatabaseMetaUrl(serverUrl, deviceId, options) {
|
|
477
|
+
const base = resolveApiBase(serverUrl, options);
|
|
478
|
+
return `${base}${PROJECT_DEVICE_EXPORTS_API_PATH}/${encodeURIComponent(deviceId)}/database/meta`;
|
|
479
|
+
}
|
|
480
|
+
function resolveDeviceExportsUrl(serverUrl, options) {
|
|
481
|
+
const base = resolveApiBase(serverUrl, options);
|
|
482
|
+
return `${base}${PROJECT_DEVICE_EXPORTS_API_PATH}`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/client/deviceDatabase.ts
|
|
486
|
+
async function fetchJson(url, errorLabel) {
|
|
487
|
+
let response;
|
|
488
|
+
try {
|
|
489
|
+
response = await fetch(url);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
const networkMessage = error instanceof Error ? error.message : String(error);
|
|
492
|
+
throw new Error(`Network error fetching ${errorLabel} from ${url}. ${networkMessage}`);
|
|
493
|
+
}
|
|
494
|
+
if (!response.ok) {
|
|
495
|
+
throw new Error(`Failed to fetch ${errorLabel} from ${url}: ${response.status} ${response.statusText}`);
|
|
496
|
+
}
|
|
497
|
+
return await response.json();
|
|
498
|
+
}
|
|
499
|
+
async function fetchDeviceExportMeta(serverUrl, deviceId, options) {
|
|
500
|
+
const url = resolveDeviceExportDatabaseMetaUrl(serverUrl, deviceId, options);
|
|
501
|
+
return fetchJson(url, "device export metadata");
|
|
502
|
+
}
|
|
503
|
+
async function fetchDeviceExportDatabase(serverUrl, deviceId, options) {
|
|
504
|
+
const url = resolveDeviceExportDatabaseUrl(serverUrl, deviceId, options);
|
|
505
|
+
return fetchSnapshot(url);
|
|
506
|
+
}
|
|
507
|
+
async function fetchDeviceExports(serverUrl, options) {
|
|
508
|
+
const url = resolveDeviceExportsUrl(serverUrl, options);
|
|
509
|
+
const payload = await fetchJson(url, "device exports");
|
|
510
|
+
return payload.devices ?? [];
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/client/resolveProjectDatabaseUrl.ts
|
|
514
|
+
function resolveApiBase2(serverUrl, options) {
|
|
515
|
+
if (options?.useSameOriginApi && typeof window !== "undefined") {
|
|
516
|
+
return window.location.origin;
|
|
517
|
+
}
|
|
518
|
+
if (serverUrl.startsWith("ws://") || serverUrl.startsWith("wss://")) {
|
|
519
|
+
return wsUrlToHttpUrl(serverUrl);
|
|
520
|
+
}
|
|
521
|
+
return serverUrl.replace(/\/$/, "");
|
|
522
|
+
}
|
|
523
|
+
function resolveProjectDatabaseUrl(serverUrl, options) {
|
|
524
|
+
return `${resolveApiBase2(serverUrl, options)}${PROJECT_DATABASE_API_PATH}`;
|
|
525
|
+
}
|
|
526
|
+
function resolveProjectDatabaseMetaUrl(serverUrl, options) {
|
|
527
|
+
return `${resolveApiBase2(serverUrl, options)}${PROJECT_DATABASE_META_API_PATH}`;
|
|
528
|
+
}
|
|
529
|
+
function resolveProjectDatabasesUrl(serverUrl, options) {
|
|
530
|
+
return `${resolveApiBase2(serverUrl, options)}${PROJECT_DATABASES_API_PATH}`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/client/projectDatabase.ts
|
|
534
|
+
async function fetchProjectDatabaseMeta(serverUrl, options) {
|
|
535
|
+
const url = resolveProjectDatabaseMetaUrl(serverUrl, options);
|
|
536
|
+
let response;
|
|
537
|
+
try {
|
|
538
|
+
response = await fetch(url);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
const networkMessage = error instanceof Error ? error.message : String(error);
|
|
541
|
+
throw new Error(`Network error fetching project database metadata from ${url}. ${networkMessage}`);
|
|
542
|
+
}
|
|
543
|
+
if (!response.ok) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
`Failed to fetch project database metadata from ${url}: ${response.status} ${response.statusText}`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return await response.json();
|
|
549
|
+
}
|
|
550
|
+
async function fetchProjectDatabase(serverUrl, options) {
|
|
551
|
+
const url = resolveProjectDatabaseUrl(serverUrl, options);
|
|
552
|
+
return fetchSnapshot(url);
|
|
553
|
+
}
|
|
554
|
+
async function fetchProjectDatabases(serverUrl, options) {
|
|
555
|
+
const url = resolveProjectDatabasesUrl(serverUrl, options);
|
|
556
|
+
let response;
|
|
557
|
+
try {
|
|
558
|
+
response = await fetch(url);
|
|
559
|
+
} catch (error) {
|
|
560
|
+
const networkMessage = error instanceof Error ? error.message : String(error);
|
|
561
|
+
throw new Error(`Network error fetching project databases from ${url}. ${networkMessage}`);
|
|
562
|
+
}
|
|
563
|
+
if (!response.ok) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
`Failed to fetch project databases from ${url}: ${response.status} ${response.statusText}`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
const payload = await response.json();
|
|
569
|
+
return payload.databases ?? [];
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/client/snapshotUpload.ts
|
|
573
|
+
function createSnapshotUploadBody(bytes) {
|
|
574
|
+
return bytes;
|
|
575
|
+
}
|
|
576
|
+
function resolveSnapshotUploadUrl(uploadUrl, hubWsUrl) {
|
|
577
|
+
const hubHttpBase = wsUrlToHttpUrl(hubWsUrl);
|
|
578
|
+
const target = new URL(uploadUrl);
|
|
579
|
+
const hub = new URL(hubHttpBase.endsWith("/") ? hubHttpBase : `${hubHttpBase}/`);
|
|
580
|
+
target.protocol = hub.protocol;
|
|
581
|
+
target.hostname = hub.hostname;
|
|
582
|
+
target.port = hub.port;
|
|
583
|
+
return target.toString();
|
|
584
|
+
}
|
|
585
|
+
async function uploadSnapshot(uploadUrl, bytes, options) {
|
|
586
|
+
const headers = {
|
|
587
|
+
"Content-Type": "application/octet-stream",
|
|
588
|
+
[options.kindHeader]: options.kind,
|
|
589
|
+
[options.mimeHeader]: options.mimeType
|
|
590
|
+
};
|
|
591
|
+
if (options.databaseName && options.nameHeader) {
|
|
592
|
+
headers[options.nameHeader] = options.databaseName;
|
|
593
|
+
}
|
|
594
|
+
const response = await fetch(uploadUrl, {
|
|
595
|
+
method: "POST",
|
|
596
|
+
headers,
|
|
597
|
+
body: createSnapshotUploadBody(bytes)
|
|
598
|
+
});
|
|
599
|
+
if (!response.ok) {
|
|
600
|
+
throw new Error(`Upload failed with status ${response.status}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/client/handleDeviceSnapshot.ts
|
|
605
|
+
async function handleDeviceSnapshotUpload(database, message, options = {}) {
|
|
606
|
+
if (options.deviceId && message.deviceId !== options.deviceId) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (!database) {
|
|
610
|
+
throw new Error("No database adapter configured");
|
|
611
|
+
}
|
|
612
|
+
if (!options.hubServerUrl) {
|
|
613
|
+
throw new Error("hubServerUrl is required to upload a device snapshot");
|
|
614
|
+
}
|
|
615
|
+
const snapshot = await database.exportSnapshot();
|
|
616
|
+
const hubHttp = wsUrlToHttpUrl(options.hubServerUrl);
|
|
617
|
+
const uploadUrl = buildDeviceSnapshotUrl(hubHttp, message.deviceId);
|
|
618
|
+
await uploadSnapshot(uploadUrl, snapshot.bytes, {
|
|
619
|
+
kind: snapshot.kind,
|
|
620
|
+
mimeType: snapshot.mimeType,
|
|
621
|
+
kindHeader: SNAPSHOT_KIND_HEADER,
|
|
622
|
+
mimeHeader: SNAPSHOT_MIME_HEADER,
|
|
623
|
+
databaseName: database.name,
|
|
624
|
+
nameHeader: SNAPSHOT_NAME_HEADER
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/client/formatSyncError.ts
|
|
629
|
+
function formatSyncErrorMessage(code, message) {
|
|
630
|
+
return formatRefreshErrorMessage(code, message);
|
|
631
|
+
}
|
|
632
|
+
function formatRefreshErrorMessage(code, message) {
|
|
633
|
+
switch (code) {
|
|
634
|
+
case "DEVICE_OFFLINE":
|
|
635
|
+
return "Selected device disconnected. Reconnect the mobile app and try again.";
|
|
636
|
+
case "REFRESH_IN_PROGRESS":
|
|
637
|
+
return "A refresh is already in progress for this device.";
|
|
638
|
+
case "EXPORT_FAILED":
|
|
639
|
+
return `Device failed to export or upload snapshot: ${message}`;
|
|
640
|
+
case "UPLOAD_FAILED":
|
|
641
|
+
return `Snapshot upload failed on the hub: ${message}`;
|
|
642
|
+
case "TIMEOUT":
|
|
643
|
+
return "Timed out waiting for the device to upload a snapshot.";
|
|
644
|
+
case "SNAPSHOT_NOT_FOUND":
|
|
645
|
+
return "Snapshot expired or was not found. Click Refresh again.";
|
|
646
|
+
case "INVALID_REQUEST":
|
|
647
|
+
return `Invalid refresh request: ${message}`;
|
|
648
|
+
default:
|
|
649
|
+
return message;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export { createDevToolsClient, createSnapshotUploadBody, fetchDeviceExportDatabase, fetchDeviceExportMeta, fetchDeviceExports, fetchProjectDatabase, fetchProjectDatabaseMeta, fetchProjectDatabases, fetchSnapshot, formatRefreshErrorMessage, formatSyncErrorMessage, handleDeviceSnapshotUpload, resolveDeviceExportDatabaseMetaUrl, resolveDeviceExportDatabaseUrl, resolveDeviceExportsUrl, resolveProjectDatabaseMetaUrl, resolveProjectDatabaseUrl, resolveProjectDatabasesUrl, resolveSnapshotUploadUrl, uploadSnapshot };
|
|
654
|
+
//# sourceMappingURL=chunk-ZS5MZR75.js.map
|
|
655
|
+
//# sourceMappingURL=chunk-ZS5MZR75.js.map
|