@weave-apps/sdk 0.10.0 → 0.12.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/dist/WeaveDOMAPI.d.ts +66 -0
- package/dist/WeaveDOMAPI.d.ts.map +1 -1
- package/dist/WeaveDOMAPI.js +51 -0
- package/dist/apis/api-mono/services/interfaces/WorkflowsTriggersWorkflowCompanyIdPostPost.d.ts +224 -0
- package/dist/apis/api-mono/services/interfaces/WorkflowsTriggersWorkflowCompanyIdPostPost.d.ts.map +1 -0
- package/dist/apis/api-mono/services/interfaces/WorkflowsTriggersWorkflowCompanyIdPostPost.js +66 -0
- package/dist/app-sdk/src/WeaveAPIClient.d.ts +373 -0
- package/dist/app-sdk/src/WeaveAPIClient.d.ts.map +1 -0
- package/dist/app-sdk/src/WeaveAPIClient.js +361 -0
- package/dist/app-sdk/src/WeaveAppInstanceAPI.d.ts +237 -0
- package/dist/app-sdk/src/WeaveAppInstanceAPI.d.ts.map +1 -0
- package/dist/app-sdk/src/WeaveAppInstanceAPI.js +395 -0
- package/dist/app-sdk/src/WeaveBackgroundAPI.d.ts +81 -0
- package/dist/app-sdk/src/WeaveBackgroundAPI.d.ts.map +1 -0
- package/dist/app-sdk/src/WeaveBackgroundAPI.js +165 -0
- package/dist/app-sdk/src/WeaveBaseApp.d.ts +318 -0
- package/dist/app-sdk/src/WeaveBaseApp.d.ts.map +1 -0
- package/dist/app-sdk/src/WeaveBaseApp.js +434 -0
- package/dist/app-sdk/src/WeaveCronAPI.d.ts +68 -0
- package/dist/app-sdk/src/WeaveCronAPI.d.ts.map +1 -0
- package/dist/app-sdk/src/WeaveCronAPI.js +172 -0
- package/dist/app-sdk/src/WeaveDOMAPI.d.ts +593 -0
- package/dist/app-sdk/src/WeaveDOMAPI.d.ts.map +1 -0
- package/dist/app-sdk/src/WeaveDOMAPI.js +774 -0
- package/dist/app-sdk/src/global.d.ts +25 -0
- package/dist/app-sdk/src/global.d.ts.map +1 -0
- package/dist/app-sdk/src/global.js +23 -0
- package/dist/app-sdk/src/index.d.ts +14 -0
- package/dist/app-sdk/src/index.d.ts.map +1 -0
- package/dist/app-sdk/src/index.js +14 -0
- package/package.json +3 -3
- package/templates/WEAVE_SPEC.md +459 -2
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeaveAppInstanceAPI
|
|
3
|
+
*
|
|
4
|
+
* Client-side API for Weave apps to discover and communicate with
|
|
5
|
+
* other instances of themselves across browser tabs.
|
|
6
|
+
*
|
|
7
|
+
* Use cases:
|
|
8
|
+
* - Prevent duplicate automation runs across tabs
|
|
9
|
+
* - Coordinate which tab is "controlling" vs "observing"
|
|
10
|
+
* - Send messages between app instances
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Register this instance
|
|
15
|
+
* const instances = await this.instances.register({ url: window.location.href });
|
|
16
|
+
*
|
|
17
|
+
* // Claim controller status (for automation)
|
|
18
|
+
* const claimed = await this.instances.claimController();
|
|
19
|
+
*
|
|
20
|
+
* // Listen for messages from other instances
|
|
21
|
+
* this.instances.onMessage((msg, sender) => {
|
|
22
|
+
* if (msg.type === 'YIELD_CONTROL') {
|
|
23
|
+
* // Another tab wants control
|
|
24
|
+
* }
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Broadcast to all other instances
|
|
28
|
+
* await this.instances.broadcast({ type: 'YIELD_CONTROL' });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
// Request timeout in ms
|
|
32
|
+
const REQUEST_TIMEOUT = 5000;
|
|
33
|
+
// Counter for unique request IDs
|
|
34
|
+
let requestIdCounter = 0;
|
|
35
|
+
// Pending requests waiting for response
|
|
36
|
+
const pendingRequests = new Map();
|
|
37
|
+
// Flag to track if listener is set up
|
|
38
|
+
let listenerInitialized = false;
|
|
39
|
+
// Global callbacks for instance messages (keyed by appId)
|
|
40
|
+
const messageCallbacks = new Map();
|
|
41
|
+
// Global callbacks for instance changes (keyed by appId)
|
|
42
|
+
const changeCallbacks = new Map();
|
|
43
|
+
/**
|
|
44
|
+
* Initialize the response listener (called once)
|
|
45
|
+
*/
|
|
46
|
+
function initializeListener() {
|
|
47
|
+
if (listenerInitialized)
|
|
48
|
+
return;
|
|
49
|
+
listenerInitialized = true;
|
|
50
|
+
window.addEventListener('message', (event) => {
|
|
51
|
+
const message = event.data;
|
|
52
|
+
// Handle app instance responses
|
|
53
|
+
if (message?.type === 'APP_INSTANCE_RESPONSE') {
|
|
54
|
+
const response = message;
|
|
55
|
+
const pending = pendingRequests.get(response.requestId);
|
|
56
|
+
if (!pending)
|
|
57
|
+
return;
|
|
58
|
+
// Clear timeout and remove from pending
|
|
59
|
+
clearTimeout(pending.timeout);
|
|
60
|
+
pendingRequests.delete(response.requestId);
|
|
61
|
+
if (response.success) {
|
|
62
|
+
pending.resolve(response);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
pending.reject(new Error(response.error || 'Unknown error'));
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Handle incoming instance messages from other tabs
|
|
70
|
+
if (message?.type === 'APP_INSTANCE_MESSAGE') {
|
|
71
|
+
const { appId, message: instanceMessage } = message.payload;
|
|
72
|
+
const callbacks = messageCallbacks.get(appId);
|
|
73
|
+
if (callbacks) {
|
|
74
|
+
for (const callback of callbacks) {
|
|
75
|
+
try {
|
|
76
|
+
callback(instanceMessage);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error('[WeaveAppInstanceAPI] Error in message callback:', error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Handle instance change notifications
|
|
86
|
+
if (message?.type === 'APP_INSTANCE_CHANGE') {
|
|
87
|
+
const { appId, instances } = message.payload;
|
|
88
|
+
const callbacks = changeCallbacks.get(appId);
|
|
89
|
+
if (callbacks) {
|
|
90
|
+
for (const callback of callbacks) {
|
|
91
|
+
try {
|
|
92
|
+
callback(instances);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error('[WeaveAppInstanceAPI] Error in change callback:', error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Send a message to the background service via content script
|
|
105
|
+
*/
|
|
106
|
+
async function sendMessage(type, payload) {
|
|
107
|
+
// Initialize listener on first use
|
|
108
|
+
initializeListener();
|
|
109
|
+
const requestId = `app-${++requestIdCounter}-${Date.now()}`;
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
// Set up timeout
|
|
112
|
+
const timeout = setTimeout(() => {
|
|
113
|
+
pendingRequests.delete(requestId);
|
|
114
|
+
reject(new Error(`App instance request timed out: ${type}`));
|
|
115
|
+
}, REQUEST_TIMEOUT);
|
|
116
|
+
// Store pending request
|
|
117
|
+
pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
118
|
+
// Send message to content script (which forwards to background)
|
|
119
|
+
window.parent.postMessage({
|
|
120
|
+
type,
|
|
121
|
+
requestId,
|
|
122
|
+
payload,
|
|
123
|
+
}, '*');
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* WeaveAppInstanceAPI class
|
|
128
|
+
*
|
|
129
|
+
* Provides methods for apps to discover and communicate with
|
|
130
|
+
* other instances of themselves across browser tabs.
|
|
131
|
+
*/
|
|
132
|
+
export class WeaveAppInstanceAPI {
|
|
133
|
+
constructor(appId) {
|
|
134
|
+
this._tabId = null;
|
|
135
|
+
this.appId = appId;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get this instance's tab ID (available after register)
|
|
139
|
+
*/
|
|
140
|
+
get tabId() {
|
|
141
|
+
return this._tabId;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Register this app instance
|
|
145
|
+
* Call this when your app starts to make it discoverable by other instances.
|
|
146
|
+
*
|
|
147
|
+
* @param metadata - Optional metadata to associate with this instance
|
|
148
|
+
* @returns All current instances of this app (including this one)
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* const instances = await this.instances.register({
|
|
152
|
+
* url: window.location.href,
|
|
153
|
+
* startedAt: Date.now()
|
|
154
|
+
* });
|
|
155
|
+
* console.log(`There are ${instances.length} instances of this app`);
|
|
156
|
+
*/
|
|
157
|
+
async register(metadata) {
|
|
158
|
+
const response = await sendMessage('APP_REGISTER_INSTANCE', {
|
|
159
|
+
appId: this.appId,
|
|
160
|
+
url: window.location.href,
|
|
161
|
+
metadata,
|
|
162
|
+
});
|
|
163
|
+
// Store our tab ID
|
|
164
|
+
if (response.tabId) {
|
|
165
|
+
this._tabId = response.tabId;
|
|
166
|
+
}
|
|
167
|
+
return response.instances || [];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Unregister this app instance
|
|
171
|
+
* Call this when your app is being destroyed.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* await this.instances.unregister();
|
|
175
|
+
*/
|
|
176
|
+
async unregister() {
|
|
177
|
+
await sendMessage('APP_UNREGISTER_INSTANCE', {
|
|
178
|
+
appId: this.appId,
|
|
179
|
+
});
|
|
180
|
+
this._tabId = null;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get all instances of this app
|
|
184
|
+
*
|
|
185
|
+
* @returns All current instances
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const instances = await this.instances.getInstances();
|
|
189
|
+
* const otherInstances = instances.filter(i => i.tabId !== this.instances.tabId);
|
|
190
|
+
*/
|
|
191
|
+
async getInstances() {
|
|
192
|
+
const response = await sendMessage('APP_GET_INSTANCES', {
|
|
193
|
+
appId: this.appId,
|
|
194
|
+
});
|
|
195
|
+
return response.instances || [];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Claim controller status for this instance
|
|
199
|
+
* Only one instance can be controller at a time.
|
|
200
|
+
*
|
|
201
|
+
* @param force - If true, forcibly take control from current controller
|
|
202
|
+
* @returns true if successfully claimed, false if another instance is controller
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* const claimed = await this.instances.claimController();
|
|
206
|
+
* if (claimed) {
|
|
207
|
+
* // This instance is now the controller
|
|
208
|
+
* this.startAutomation();
|
|
209
|
+
* } else {
|
|
210
|
+
* // Another instance is already controlling
|
|
211
|
+
* this.showObserverUI();
|
|
212
|
+
* }
|
|
213
|
+
*/
|
|
214
|
+
async claimController(force = false) {
|
|
215
|
+
const response = await sendMessage('APP_CLAIM_CONTROLLER', {
|
|
216
|
+
appId: this.appId,
|
|
217
|
+
force,
|
|
218
|
+
});
|
|
219
|
+
return response.claimed ?? false;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Release controller status
|
|
223
|
+
* Call this when you want to give up control.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* await this.instances.releaseController();
|
|
227
|
+
*/
|
|
228
|
+
async releaseController() {
|
|
229
|
+
await sendMessage('APP_RELEASE_CONTROLLER', {
|
|
230
|
+
appId: this.appId,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Check if this instance is the controller
|
|
235
|
+
*
|
|
236
|
+
* @returns true if this instance is the controller
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* if (await this.instances.isController()) {
|
|
240
|
+
* // We're in control
|
|
241
|
+
* }
|
|
242
|
+
*/
|
|
243
|
+
async isController() {
|
|
244
|
+
const response = await sendMessage('APP_IS_CONTROLLER', {
|
|
245
|
+
appId: this.appId,
|
|
246
|
+
});
|
|
247
|
+
return response.isController ?? false;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get the current controller instance
|
|
251
|
+
*
|
|
252
|
+
* @returns The controller instance, or null if none
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* const controller = await this.instances.getController();
|
|
256
|
+
* if (controller) {
|
|
257
|
+
* console.log(`Controller is in tab ${controller.tabId}`);
|
|
258
|
+
* }
|
|
259
|
+
*/
|
|
260
|
+
async getController() {
|
|
261
|
+
const response = await sendMessage('APP_GET_CONTROLLER', {
|
|
262
|
+
appId: this.appId,
|
|
263
|
+
});
|
|
264
|
+
return response.controller ?? null;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Broadcast a message to all other instances
|
|
268
|
+
*
|
|
269
|
+
* @param message - Message to send (type and optional data)
|
|
270
|
+
* @returns Array of tab IDs that received the message
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* await this.instances.broadcast({
|
|
274
|
+
* type: 'YIELD_CONTROL',
|
|
275
|
+
* data: { reason: 'User requested control' }
|
|
276
|
+
* });
|
|
277
|
+
*/
|
|
278
|
+
async broadcast(message) {
|
|
279
|
+
const response = await sendMessage('APP_BROADCAST', {
|
|
280
|
+
appId: this.appId,
|
|
281
|
+
instanceMessage: message,
|
|
282
|
+
});
|
|
283
|
+
return response.recipientTabIds || [];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Send a message to the controller instance
|
|
287
|
+
*
|
|
288
|
+
* @param message - Message to send
|
|
289
|
+
* @returns Tab ID of the controller, or null if no controller
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* await this.instances.sendToController({
|
|
293
|
+
* type: 'REQUEST_STATUS',
|
|
294
|
+
* });
|
|
295
|
+
*/
|
|
296
|
+
async sendToController(message) {
|
|
297
|
+
const response = await sendMessage('APP_SEND_TO_CONTROLLER', {
|
|
298
|
+
appId: this.appId,
|
|
299
|
+
instanceMessage: message,
|
|
300
|
+
});
|
|
301
|
+
return response.controllerTabId ?? null;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Send a message to a specific tab
|
|
305
|
+
*
|
|
306
|
+
* @param targetTabId - Tab ID to send to
|
|
307
|
+
* @param message - Message to send
|
|
308
|
+
* @returns true if sent successfully
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* await this.instances.sendToTab(123, {
|
|
312
|
+
* type: 'PING',
|
|
313
|
+
* });
|
|
314
|
+
*/
|
|
315
|
+
async sendToTab(targetTabId, message) {
|
|
316
|
+
const response = await sendMessage('APP_SEND_TO_TAB', {
|
|
317
|
+
appId: this.appId,
|
|
318
|
+
targetTabId,
|
|
319
|
+
instanceMessage: message,
|
|
320
|
+
});
|
|
321
|
+
return response.sent ?? false;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Listen for messages from other instances
|
|
325
|
+
*
|
|
326
|
+
* @param callback - Function to call when a message is received
|
|
327
|
+
* @returns Unsubscribe function
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* const unsubscribe = this.instances.onMessage((msg) => {
|
|
331
|
+
* if (msg.type === 'YIELD_CONTROL') {
|
|
332
|
+
* this.releaseController();
|
|
333
|
+
* this.showObserverUI();
|
|
334
|
+
* }
|
|
335
|
+
* });
|
|
336
|
+
*
|
|
337
|
+
* // Later, to stop listening:
|
|
338
|
+
* unsubscribe();
|
|
339
|
+
*/
|
|
340
|
+
onMessage(callback) {
|
|
341
|
+
// Initialize listener
|
|
342
|
+
initializeListener();
|
|
343
|
+
// Get or create callback set for this app
|
|
344
|
+
let callbacks = messageCallbacks.get(this.appId);
|
|
345
|
+
if (!callbacks) {
|
|
346
|
+
callbacks = new Set();
|
|
347
|
+
messageCallbacks.set(this.appId, callbacks);
|
|
348
|
+
}
|
|
349
|
+
callbacks.add(callback);
|
|
350
|
+
// Return unsubscribe function
|
|
351
|
+
return () => {
|
|
352
|
+
callbacks?.delete(callback);
|
|
353
|
+
if (callbacks?.size === 0) {
|
|
354
|
+
messageCallbacks.delete(this.appId);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Listen for instance changes (new instances, removed instances)
|
|
360
|
+
*
|
|
361
|
+
* @param callback - Function to call when instances change
|
|
362
|
+
* @returns Unsubscribe function
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* const unsubscribe = this.instances.onInstanceChange((instances) => {
|
|
366
|
+
* console.log(`Now ${instances.length} instances`);
|
|
367
|
+
* const controller = instances.find(i => i.isController);
|
|
368
|
+
* if (controller) {
|
|
369
|
+
* console.log(`Controller is tab ${controller.tabId}`);
|
|
370
|
+
* }
|
|
371
|
+
* });
|
|
372
|
+
*/
|
|
373
|
+
onInstanceChange(callback) {
|
|
374
|
+
// Initialize listener
|
|
375
|
+
initializeListener();
|
|
376
|
+
// Get or create callback set for this app
|
|
377
|
+
let callbacks = changeCallbacks.get(this.appId);
|
|
378
|
+
if (!callbacks) {
|
|
379
|
+
callbacks = new Set();
|
|
380
|
+
changeCallbacks.set(this.appId, callbacks);
|
|
381
|
+
}
|
|
382
|
+
callbacks.add(callback);
|
|
383
|
+
// Return unsubscribe function
|
|
384
|
+
return () => {
|
|
385
|
+
callbacks?.delete(callback);
|
|
386
|
+
if (callbacks?.size === 0) {
|
|
387
|
+
changeCallbacks.delete(this.appId);
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Export for global usage
|
|
393
|
+
if (typeof window !== 'undefined') {
|
|
394
|
+
window.WeaveAppInstanceAPI = WeaveAppInstanceAPI;
|
|
395
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeaveBackgroundAPI
|
|
3
|
+
*
|
|
4
|
+
* Client-side API for Weave apps to interact with the background state service.
|
|
5
|
+
* Allows apps to persist state across page reloads and manage multi-page operations.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Save state (survives page reload)
|
|
10
|
+
* await weaveAPI.background.saveState({ count: 5, items: [...] });
|
|
11
|
+
*
|
|
12
|
+
* // Load state after page reload
|
|
13
|
+
* const state = await weaveAPI.background.loadState();
|
|
14
|
+
*
|
|
15
|
+
* // Set pending operation before navigation
|
|
16
|
+
* await weaveAPI.background.setPendingOperation({
|
|
17
|
+
* type: 'fill-form',
|
|
18
|
+
* data: { content: '...' },
|
|
19
|
+
* targetUrl: 'https://example.com/form'
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Check for pending operation after navigation
|
|
23
|
+
* const pending = await weaveAPI.background.getPendingOperation();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Pending operation for multi-page flows
|
|
28
|
+
*/
|
|
29
|
+
export interface PendingOperation {
|
|
30
|
+
type: string;
|
|
31
|
+
data: any;
|
|
32
|
+
targetUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* WeaveBackgroundAPI class
|
|
36
|
+
*
|
|
37
|
+
* Provides methods for apps to interact with background state service.
|
|
38
|
+
* Each app instance gets its own API instance with its appId.
|
|
39
|
+
*/
|
|
40
|
+
export declare class WeaveBackgroundAPI {
|
|
41
|
+
private appId;
|
|
42
|
+
constructor(appId: string);
|
|
43
|
+
/**
|
|
44
|
+
* Save app state to background service
|
|
45
|
+
* State survives page reloads within the browser session
|
|
46
|
+
*
|
|
47
|
+
* @param state - The state object to save
|
|
48
|
+
*/
|
|
49
|
+
saveState(state: any): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Load app state from background service
|
|
52
|
+
* Returns null if no state exists or if it has expired
|
|
53
|
+
*
|
|
54
|
+
* @returns The saved state or null
|
|
55
|
+
*/
|
|
56
|
+
loadState<T = any>(): Promise<T | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Clear app state from background service
|
|
59
|
+
*/
|
|
60
|
+
clearState(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Set a pending operation for multi-page flows
|
|
63
|
+
* Use this before triggering navigation to save context
|
|
64
|
+
*
|
|
65
|
+
* @param operation - The operation to save
|
|
66
|
+
*/
|
|
67
|
+
setPendingOperation(operation: PendingOperation): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Get pending operation after navigation
|
|
70
|
+
* Returns null if no pending operation exists
|
|
71
|
+
*
|
|
72
|
+
* @returns The pending operation or null
|
|
73
|
+
*/
|
|
74
|
+
getPendingOperation(): Promise<PendingOperation | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Clear pending operation
|
|
77
|
+
* Call this after successfully handling the pending operation
|
|
78
|
+
*/
|
|
79
|
+
clearPendingOperation(): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=WeaveBackgroundAPI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WeaveBackgroundAPI.d.ts","sourceRoot":"","sources":["../../../src/WeaveBackgroundAPI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAWH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4FD;;;;;GAKG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAS;gBAEV,KAAK,EAAE,MAAM;IAIzB;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1C;;;;;OAKG;IACG,SAAS,CAAC,CAAC,GAAG,GAAG,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAO7C;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC;;;;;OAKG;IACG,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOrE;;;;;OAKG;IACG,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAO7D;;;OAGG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7C"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeaveBackgroundAPI
|
|
3
|
+
*
|
|
4
|
+
* Client-side API for Weave apps to interact with the background state service.
|
|
5
|
+
* Allows apps to persist state across page reloads and manage multi-page operations.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Save state (survives page reload)
|
|
10
|
+
* await weaveAPI.background.saveState({ count: 5, items: [...] });
|
|
11
|
+
*
|
|
12
|
+
* // Load state after page reload
|
|
13
|
+
* const state = await weaveAPI.background.loadState();
|
|
14
|
+
*
|
|
15
|
+
* // Set pending operation before navigation
|
|
16
|
+
* await weaveAPI.background.setPendingOperation({
|
|
17
|
+
* type: 'fill-form',
|
|
18
|
+
* data: { content: '...' },
|
|
19
|
+
* targetUrl: 'https://example.com/form'
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Check for pending operation after navigation
|
|
23
|
+
* const pending = await weaveAPI.background.getPendingOperation();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
// Request timeout in ms
|
|
27
|
+
const REQUEST_TIMEOUT = 5000;
|
|
28
|
+
// Counter for unique request IDs
|
|
29
|
+
let requestIdCounter = 0;
|
|
30
|
+
// Pending requests waiting for response
|
|
31
|
+
const pendingRequests = new Map();
|
|
32
|
+
// Flag to track if listener is set up
|
|
33
|
+
let listenerInitialized = false;
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the response listener (called once)
|
|
36
|
+
*/
|
|
37
|
+
function initializeListener() {
|
|
38
|
+
if (listenerInitialized)
|
|
39
|
+
return;
|
|
40
|
+
listenerInitialized = true;
|
|
41
|
+
window.addEventListener('message', (event) => {
|
|
42
|
+
const message = event.data;
|
|
43
|
+
// Only handle background state responses
|
|
44
|
+
if (message?.type !== 'BG_STATE_RESPONSE')
|
|
45
|
+
return;
|
|
46
|
+
const pending = pendingRequests.get(message.requestId);
|
|
47
|
+
if (!pending)
|
|
48
|
+
return;
|
|
49
|
+
// Clear timeout and remove from pending
|
|
50
|
+
clearTimeout(pending.timeout);
|
|
51
|
+
pendingRequests.delete(message.requestId);
|
|
52
|
+
if (message.success) {
|
|
53
|
+
pending.resolve({
|
|
54
|
+
state: message.state,
|
|
55
|
+
operation: message.operation,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
pending.reject(new Error(message.error || 'Unknown error'));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Send a message to the background state service via content script
|
|
65
|
+
*/
|
|
66
|
+
async function sendMessage(type, payload) {
|
|
67
|
+
// Initialize listener on first use
|
|
68
|
+
initializeListener();
|
|
69
|
+
const requestId = `bg-${++requestIdCounter}-${Date.now()}`;
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
// Set up timeout
|
|
72
|
+
const timeout = setTimeout(() => {
|
|
73
|
+
pendingRequests.delete(requestId);
|
|
74
|
+
reject(new Error(`Background state request timed out: ${type}`));
|
|
75
|
+
}, REQUEST_TIMEOUT);
|
|
76
|
+
// Store pending request
|
|
77
|
+
pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
78
|
+
// Send message to content script (which forwards to background)
|
|
79
|
+
window.parent.postMessage({
|
|
80
|
+
type,
|
|
81
|
+
requestId,
|
|
82
|
+
payload,
|
|
83
|
+
}, '*');
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* WeaveBackgroundAPI class
|
|
88
|
+
*
|
|
89
|
+
* Provides methods for apps to interact with background state service.
|
|
90
|
+
* Each app instance gets its own API instance with its appId.
|
|
91
|
+
*/
|
|
92
|
+
export class WeaveBackgroundAPI {
|
|
93
|
+
constructor(appId) {
|
|
94
|
+
this.appId = appId;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Save app state to background service
|
|
98
|
+
* State survives page reloads within the browser session
|
|
99
|
+
*
|
|
100
|
+
* @param state - The state object to save
|
|
101
|
+
*/
|
|
102
|
+
async saveState(state) {
|
|
103
|
+
await sendMessage('BG_SAVE_STATE', {
|
|
104
|
+
appId: this.appId,
|
|
105
|
+
state,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Load app state from background service
|
|
110
|
+
* Returns null if no state exists or if it has expired
|
|
111
|
+
*
|
|
112
|
+
* @returns The saved state or null
|
|
113
|
+
*/
|
|
114
|
+
async loadState() {
|
|
115
|
+
const response = await sendMessage('BG_LOAD_STATE', {
|
|
116
|
+
appId: this.appId,
|
|
117
|
+
});
|
|
118
|
+
return response.state;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Clear app state from background service
|
|
122
|
+
*/
|
|
123
|
+
async clearState() {
|
|
124
|
+
await sendMessage('BG_CLEAR_STATE', {
|
|
125
|
+
appId: this.appId,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Set a pending operation for multi-page flows
|
|
130
|
+
* Use this before triggering navigation to save context
|
|
131
|
+
*
|
|
132
|
+
* @param operation - The operation to save
|
|
133
|
+
*/
|
|
134
|
+
async setPendingOperation(operation) {
|
|
135
|
+
await sendMessage('BG_SET_PENDING_OP', {
|
|
136
|
+
appId: this.appId,
|
|
137
|
+
operation,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get pending operation after navigation
|
|
142
|
+
* Returns null if no pending operation exists
|
|
143
|
+
*
|
|
144
|
+
* @returns The pending operation or null
|
|
145
|
+
*/
|
|
146
|
+
async getPendingOperation() {
|
|
147
|
+
const response = await sendMessage('BG_GET_PENDING_OP', {
|
|
148
|
+
appId: this.appId,
|
|
149
|
+
});
|
|
150
|
+
return response.operation;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Clear pending operation
|
|
154
|
+
* Call this after successfully handling the pending operation
|
|
155
|
+
*/
|
|
156
|
+
async clearPendingOperation() {
|
|
157
|
+
await sendMessage('BG_CLEAR_PENDING_OP', {
|
|
158
|
+
appId: this.appId,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Export for global usage
|
|
163
|
+
if (typeof window !== 'undefined') {
|
|
164
|
+
window.WeaveBackgroundAPI = WeaveBackgroundAPI;
|
|
165
|
+
}
|