@weave-apps/sdk 0.1.6 → 0.1.8
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 +109 -14
- package/dist/WeaveBackgroundAPI.d.ts +81 -0
- package/dist/WeaveBackgroundAPI.d.ts.map +1 -0
- package/dist/WeaveBackgroundAPI.js +165 -0
- package/dist/WeaveBaseApp.d.ts +43 -3
- package/dist/WeaveBaseApp.d.ts.map +1 -1
- package/dist/WeaveBaseApp.js +92 -6
- package/dist/global.d.ts +2 -0
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js +2 -0
- package/package.json +1 -1
- package/scripts/copy-sdk-files.js +1 -0
- package/templates/WEAVE_SPEC.md +264 -7
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ Official SDK for building third-party applications for the Weave Platform.
|
|
|
11
11
|
- ✅ **DOM API** - Secure parent page DOM manipulation
|
|
12
12
|
- ✅ **Shadow DOM Isolation** - Scoped styles and encapsulation
|
|
13
13
|
- ✅ **Background Services** - Run code without user interaction
|
|
14
|
+
- ✅ **State Persistence** - Survive page reloads with auto-persist
|
|
15
|
+
- ✅ **Multi-Page Flows** - Pending operations across navigations
|
|
14
16
|
- ✅ **Form Integration** - Auto-fill forms with extracted data
|
|
15
17
|
- ✅ **AI Integration** - Leverage AI for smart form filling
|
|
16
18
|
- ✅ **Data Persistence** - Store app data with the Weave API
|
|
@@ -124,6 +126,8 @@ constructor(appInfo: WeaveAppInfo)
|
|
|
124
126
|
- `description` (string) - Detailed description
|
|
125
127
|
- `author` (string) - Author name
|
|
126
128
|
- `tags` (string[])` - Optional tags
|
|
129
|
+
- `persistState` (boolean) - Enable auto-persist across page reloads (default: false)
|
|
130
|
+
- `persistDebounce` (number) - Debounce delay in ms for state saves (default: 100)
|
|
127
131
|
|
|
128
132
|
#### Settings & State
|
|
129
133
|
|
|
@@ -223,12 +227,35 @@ this.renderHTML('<h1>Hello</h1>');
|
|
|
223
227
|
```
|
|
224
228
|
|
|
225
229
|
##### `setState(updates: object): void`
|
|
226
|
-
Update app state (shallow merge).
|
|
230
|
+
Update app state (shallow merge). If `persistState: true`, state is automatically saved to background service.
|
|
227
231
|
|
|
228
232
|
```typescript
|
|
229
233
|
this.setState({ isLoading: true, count: 5 });
|
|
230
234
|
```
|
|
231
235
|
|
|
236
|
+
##### `this.background` (Property)
|
|
237
|
+
Access the background API for state persistence and pending operations.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// Save state manually
|
|
241
|
+
await this.background?.saveState({ count: 5 });
|
|
242
|
+
|
|
243
|
+
// Load state manually
|
|
244
|
+
const state = await this.background?.loadState();
|
|
245
|
+
|
|
246
|
+
// Set pending operation before navigation
|
|
247
|
+
await this.background?.setPendingOperation({
|
|
248
|
+
type: 'fill-form',
|
|
249
|
+
data: { content: '...' }
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Get pending operation after navigation
|
|
253
|
+
const pending = await this.background?.getPendingOperation();
|
|
254
|
+
|
|
255
|
+
// Clear pending operation
|
|
256
|
+
await this.background?.clearPendingOperation();
|
|
257
|
+
```
|
|
258
|
+
|
|
232
259
|
##### `query<T>(selector: string): T | null`
|
|
233
260
|
Query a single element in shadow root.
|
|
234
261
|
|
|
@@ -376,21 +403,86 @@ const markdown = window.weaveAPI.utils.htmlToMarkdown(htmlString);
|
|
|
376
403
|
const html = window.weaveAPI.utils.markdownToHtml(markdownString);
|
|
377
404
|
```
|
|
378
405
|
|
|
379
|
-
##
|
|
406
|
+
## Advanced Topics
|
|
407
|
+
|
|
408
|
+
### State Persistence (Survive Page Reloads)
|
|
380
409
|
|
|
381
|
-
|
|
410
|
+
Apps can persist state across page reloads using the background state service. There are two approaches:
|
|
382
411
|
|
|
383
|
-
|
|
384
|
-
- **URL Monitoring** - Reacts to SPA navigation
|
|
385
|
-
- **DOM Injection** - Adds UI elements to parent page
|
|
386
|
-
- **Form Detection** - Captures form structure and data
|
|
387
|
-
- **Data Persistence** - Stores consultation notes
|
|
388
|
-
- **AI Integration** - Auto-fills forms using AI
|
|
389
|
-
- **State Management** - Multi-stage workflow (idle → select-form → confirm → filling → success)
|
|
412
|
+
#### Auto-Persist (Recommended)
|
|
390
413
|
|
|
391
|
-
|
|
414
|
+
Simply set `persistState: true` in your app config. State is automatically saved on every `setState()` call and restored when the app initializes after a page reload.
|
|
392
415
|
|
|
393
|
-
|
|
416
|
+
```typescript
|
|
417
|
+
class MyApp extends WeaveBaseApp<Settings, State> {
|
|
418
|
+
constructor() {
|
|
419
|
+
super({
|
|
420
|
+
id: 'my-app',
|
|
421
|
+
name: 'My App',
|
|
422
|
+
version: '1.0.0',
|
|
423
|
+
category: 'utility',
|
|
424
|
+
description: 'App with persistent state',
|
|
425
|
+
author: 'Your Name',
|
|
426
|
+
persistState: true, // Enable auto-persist
|
|
427
|
+
persistDebounce: 100, // Optional: debounce delay (default 100ms)
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
this.state = { count: 0, items: [] };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async onBackgroundService() {
|
|
434
|
+
// State is already restored automatically!
|
|
435
|
+
console.log('Restored count:', this.state.count);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
handleIncrement() {
|
|
439
|
+
// Automatically saved to background service
|
|
440
|
+
this.setState({ count: this.state.count + 1 });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
#### Multi-Page Flows (Pending Operations)
|
|
446
|
+
|
|
447
|
+
For operations that span multiple pages (e.g., extract data on page A, fill form on page B), use pending operations:
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
class DataTransferApp extends WeaveBaseApp {
|
|
451
|
+
async handleTransferClick() {
|
|
452
|
+
// Save pending operation BEFORE navigation
|
|
453
|
+
await this.background?.setPendingOperation({
|
|
454
|
+
type: 'fill-form',
|
|
455
|
+
data: {
|
|
456
|
+
content: this.state.extractedContent,
|
|
457
|
+
targetForm: 'patient-notes'
|
|
458
|
+
},
|
|
459
|
+
targetUrl: 'https://target-system.com/form'
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Navigate to target page
|
|
463
|
+
window.open('https://target-system.com/form', '_self');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async onBackgroundService() {
|
|
467
|
+
// Check for pending operation after page loads
|
|
468
|
+
const pending = await this.background?.getPendingOperation();
|
|
469
|
+
|
|
470
|
+
if (pending?.type === 'fill-form') {
|
|
471
|
+
const pageUrl = await window.weaveDOM.getPageUrl();
|
|
472
|
+
|
|
473
|
+
// Verify we're on the right page
|
|
474
|
+
if (pageUrl.includes('target-system.com/form')) {
|
|
475
|
+
await this.fillForm(pending.data);
|
|
476
|
+
await this.background?.clearPendingOperation();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### State TTL
|
|
484
|
+
|
|
485
|
+
Persisted state expires after **5 minutes** by default. This prevents stale state from accumulating.
|
|
394
486
|
|
|
395
487
|
### Settings & Configuration
|
|
396
488
|
|
|
@@ -493,6 +585,9 @@ Check that selectors match the actual form structure. Use browser DevTools to in
|
|
|
493
585
|
### Button not injecting
|
|
494
586
|
Verify the target selector exists before injection attempt. Check browser console for errors.
|
|
495
587
|
|
|
496
|
-
### State not persisting
|
|
497
|
-
|
|
588
|
+
### State not persisting across page reloads
|
|
589
|
+
Enable `persistState: true` in your app config. State will automatically save on `setState()` and restore on page reload.
|
|
590
|
+
|
|
591
|
+
### State not persisting long-term
|
|
592
|
+
Use `this.weaveAPI.appData.*` methods to persist data to backend. Background state only lasts 5 minutes.
|
|
498
593
|
|
|
@@ -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
|
+
}
|
package/dist/WeaveBaseApp.d.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* interface MyAppSettings {
|
|
11
11
|
* apiKey: string;
|
|
12
12
|
* endpoint: string;
|
|
13
|
-
* autoSave?: boolean;
|
|
14
13
|
* }
|
|
15
14
|
*
|
|
16
15
|
* interface MyAppState {
|
|
@@ -28,7 +27,9 @@
|
|
|
28
27
|
* category: 'utility',
|
|
29
28
|
* description: 'My custom app',
|
|
30
29
|
* author: 'Your Name',
|
|
31
|
-
* tags: ['custom']
|
|
30
|
+
* tags: ['custom'],
|
|
31
|
+
* // Enable auto-persist to survive page reloads
|
|
32
|
+
* persistState: true,
|
|
32
33
|
* });
|
|
33
34
|
*
|
|
34
35
|
* // Settings are now type-safe!
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
* }
|
|
48
49
|
*
|
|
49
50
|
* setupEventListeners() {
|
|
50
|
-
* // setState is also type-safe
|
|
51
|
+
* // setState is also type-safe and auto-persists if enabled
|
|
51
52
|
* this.setState({ count: this.state.count + 1 }); // ✅ Type-safe
|
|
52
53
|
* }
|
|
53
54
|
* }
|
|
@@ -55,6 +56,8 @@
|
|
|
55
56
|
* customElements.define('my-app', MyApp);
|
|
56
57
|
* ```
|
|
57
58
|
*/
|
|
59
|
+
import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
|
|
60
|
+
export type { PendingOperation } from './WeaveBackgroundAPI';
|
|
58
61
|
/**
|
|
59
62
|
* App metadata configuration
|
|
60
63
|
*/
|
|
@@ -73,6 +76,19 @@ export interface WeaveAppInfo {
|
|
|
73
76
|
author: string;
|
|
74
77
|
/** Additional tags for search and categorization */
|
|
75
78
|
tags?: string[];
|
|
79
|
+
/**
|
|
80
|
+
* Enable automatic state persistence across page reloads.
|
|
81
|
+
* When true, state is automatically saved to background service on each setState() call.
|
|
82
|
+
* State is restored when the app initializes after a page reload.
|
|
83
|
+
* @default false
|
|
84
|
+
*/
|
|
85
|
+
persistState?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Debounce delay for state persistence in milliseconds.
|
|
88
|
+
* Prevents excessive saves when state changes rapidly.
|
|
89
|
+
* @default 100
|
|
90
|
+
*/
|
|
91
|
+
persistDebounce?: number;
|
|
76
92
|
}
|
|
77
93
|
/**
|
|
78
94
|
* Base class for Weave apps
|
|
@@ -90,11 +106,24 @@ export declare abstract class WeaveBaseApp<TSettings = Record<string, any>, TSta
|
|
|
90
106
|
private _shadowRoot;
|
|
91
107
|
/** App-specific API client instance (prevents app ID conflicts) */
|
|
92
108
|
protected weaveAPI: any;
|
|
109
|
+
/** Background API for state persistence */
|
|
110
|
+
private _backgroundAPI;
|
|
111
|
+
/** Debounced persist function */
|
|
112
|
+
private _debouncedPersist;
|
|
113
|
+
/** Flag to track if state has been restored */
|
|
114
|
+
private _stateRestored;
|
|
115
|
+
/** App UUID from registry */
|
|
116
|
+
private _appUuid;
|
|
93
117
|
/**
|
|
94
118
|
* Creates a new Weave app
|
|
95
119
|
* @param appInfo - App metadata configuration
|
|
96
120
|
*/
|
|
97
121
|
constructor(appInfo: WeaveAppInfo);
|
|
122
|
+
/**
|
|
123
|
+
* Get the background API for state persistence and pending operations
|
|
124
|
+
* Use this for manual state management or multi-page flows
|
|
125
|
+
*/
|
|
126
|
+
protected get background(): WeaveBackgroundAPI | null;
|
|
98
127
|
/**
|
|
99
128
|
* Get shadow root (override native property)
|
|
100
129
|
*/
|
|
@@ -159,8 +188,19 @@ export declare abstract class WeaveBaseApp<TSettings = Record<string, any>, TSta
|
|
|
159
188
|
setAppConfig(_config: Record<string, any>): void;
|
|
160
189
|
/**
|
|
161
190
|
* Helper: Update app state
|
|
191
|
+
* If persistState is enabled, state is automatically saved to background service
|
|
162
192
|
*/
|
|
163
193
|
protected setState(updates: Partial<TState>): void;
|
|
194
|
+
/**
|
|
195
|
+
* Persist current state to background service
|
|
196
|
+
* Called automatically when persistState is enabled
|
|
197
|
+
*/
|
|
198
|
+
private _persistState;
|
|
199
|
+
/**
|
|
200
|
+
* Restore state from background service
|
|
201
|
+
* Called by BackgroundAppManager during initialization
|
|
202
|
+
*/
|
|
203
|
+
restoreState(): Promise<boolean>;
|
|
164
204
|
/**
|
|
165
205
|
* Helper: Render HTML into shadow root
|
|
166
206
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WeaveBaseApp.d.ts","sourceRoot":"","sources":["../src/WeaveBaseApp.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"WeaveBaseApp.d.ts","sourceRoot":"","sources":["../src/WeaveBaseApp.ts"],"names":[],"mappings":"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"}
|
package/dist/WeaveBaseApp.js
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* interface MyAppSettings {
|
|
11
11
|
* apiKey: string;
|
|
12
12
|
* endpoint: string;
|
|
13
|
-
* autoSave?: boolean;
|
|
14
13
|
* }
|
|
15
14
|
*
|
|
16
15
|
* interface MyAppState {
|
|
@@ -28,7 +27,9 @@
|
|
|
28
27
|
* category: 'utility',
|
|
29
28
|
* description: 'My custom app',
|
|
30
29
|
* author: 'Your Name',
|
|
31
|
-
* tags: ['custom']
|
|
30
|
+
* tags: ['custom'],
|
|
31
|
+
* // Enable auto-persist to survive page reloads
|
|
32
|
+
* persistState: true,
|
|
32
33
|
* });
|
|
33
34
|
*
|
|
34
35
|
* // Settings are now type-safe!
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
* }
|
|
48
49
|
*
|
|
49
50
|
* setupEventListeners() {
|
|
50
|
-
* // setState is also type-safe
|
|
51
|
+
* // setState is also type-safe and auto-persists if enabled
|
|
51
52
|
* this.setState({ count: this.state.count + 1 }); // ✅ Type-safe
|
|
52
53
|
* }
|
|
53
54
|
* }
|
|
@@ -55,6 +56,22 @@
|
|
|
55
56
|
* customElements.define('my-app', MyApp);
|
|
56
57
|
* ```
|
|
57
58
|
*/
|
|
59
|
+
import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
|
|
60
|
+
/**
|
|
61
|
+
* Simple debounce utility
|
|
62
|
+
*/
|
|
63
|
+
function debounce(fn, delay) {
|
|
64
|
+
let timeoutId = null;
|
|
65
|
+
return (...args) => {
|
|
66
|
+
if (timeoutId) {
|
|
67
|
+
clearTimeout(timeoutId);
|
|
68
|
+
}
|
|
69
|
+
timeoutId = setTimeout(() => {
|
|
70
|
+
fn(...args);
|
|
71
|
+
timeoutId = null;
|
|
72
|
+
}, delay);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
58
75
|
/**
|
|
59
76
|
* Base class for Weave apps
|
|
60
77
|
* @template TSettings - Type for app settings, defaults to an empty object
|
|
@@ -69,6 +86,14 @@ export class WeaveBaseApp extends HTMLElement {
|
|
|
69
86
|
super();
|
|
70
87
|
/** App-specific state (override in subclass) */
|
|
71
88
|
this.state = {};
|
|
89
|
+
/** Background API for state persistence */
|
|
90
|
+
this._backgroundAPI = null;
|
|
91
|
+
/** Debounced persist function */
|
|
92
|
+
this._debouncedPersist = null;
|
|
93
|
+
/** Flag to track if state has been restored */
|
|
94
|
+
this._stateRestored = false;
|
|
95
|
+
/** App UUID from registry */
|
|
96
|
+
this._appUuid = null;
|
|
72
97
|
// Validate required fields
|
|
73
98
|
if (!appInfo.id || !appInfo.name || !appInfo.version) {
|
|
74
99
|
throw new Error('WeaveBaseApp: id, name, and version are required');
|
|
@@ -85,14 +110,24 @@ export class WeaveBaseApp extends HTMLElement {
|
|
|
85
110
|
if (registryData?.settings) {
|
|
86
111
|
this.appSettings = registryData.settings;
|
|
87
112
|
}
|
|
113
|
+
// Store app UUID for background API
|
|
114
|
+
this._appUuid = registryData?.uuid || null;
|
|
88
115
|
// Set app UUID on the global API client so it's included in all requests
|
|
89
116
|
// This allows background services to make API calls without the app being open
|
|
90
117
|
if (window.weaveAPI) {
|
|
91
|
-
|
|
92
|
-
if (appUuid) {
|
|
118
|
+
if (this._appUuid) {
|
|
93
119
|
// Create a new API client instance for THIS app
|
|
94
120
|
this.weaveAPI = new window.WeaveAPIClient();
|
|
95
|
-
this.weaveAPI.setAppId(
|
|
121
|
+
this.weaveAPI.setAppId(this._appUuid);
|
|
122
|
+
// Create background API instance for state persistence
|
|
123
|
+
this._backgroundAPI = new WeaveBackgroundAPI(this._appUuid);
|
|
124
|
+
// Set up debounced persist if enabled
|
|
125
|
+
if (appInfo.persistState) {
|
|
126
|
+
const delay = appInfo.persistDebounce ?? 100;
|
|
127
|
+
this._debouncedPersist = debounce(() => {
|
|
128
|
+
this._persistState();
|
|
129
|
+
}, delay);
|
|
130
|
+
}
|
|
96
131
|
}
|
|
97
132
|
else {
|
|
98
133
|
console.warn('⚠️ App UUID not found in registry - API calls may fail. App:', appInfo.id);
|
|
@@ -101,6 +136,13 @@ export class WeaveBaseApp extends HTMLElement {
|
|
|
101
136
|
}
|
|
102
137
|
}
|
|
103
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Get the background API for state persistence and pending operations
|
|
141
|
+
* Use this for manual state management or multi-page flows
|
|
142
|
+
*/
|
|
143
|
+
get background() {
|
|
144
|
+
return this._backgroundAPI;
|
|
145
|
+
}
|
|
104
146
|
/**
|
|
105
147
|
* Get shadow root (override native property)
|
|
106
148
|
*/
|
|
@@ -195,9 +237,53 @@ export class WeaveBaseApp extends HTMLElement {
|
|
|
195
237
|
}
|
|
196
238
|
/**
|
|
197
239
|
* Helper: Update app state
|
|
240
|
+
* If persistState is enabled, state is automatically saved to background service
|
|
198
241
|
*/
|
|
199
242
|
setState(updates) {
|
|
200
243
|
this.state = { ...this.state, ...updates };
|
|
244
|
+
// Auto-persist if enabled
|
|
245
|
+
if (this.appInfo.persistState && this._debouncedPersist) {
|
|
246
|
+
this._debouncedPersist();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Persist current state to background service
|
|
251
|
+
* Called automatically when persistState is enabled
|
|
252
|
+
*/
|
|
253
|
+
async _persistState() {
|
|
254
|
+
if (!this._backgroundAPI)
|
|
255
|
+
return;
|
|
256
|
+
try {
|
|
257
|
+
await this._backgroundAPI.saveState(this.state);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error('❌ Failed to persist state:', error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Restore state from background service
|
|
265
|
+
* Called by BackgroundAppManager during initialization
|
|
266
|
+
*/
|
|
267
|
+
async restoreState() {
|
|
268
|
+
if (!this._backgroundAPI || !this.appInfo.persistState) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (this._stateRestored) {
|
|
272
|
+
return true; // Already restored
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const savedState = await this._backgroundAPI.loadState();
|
|
276
|
+
if (savedState) {
|
|
277
|
+
this.state = { ...this.state, ...savedState };
|
|
278
|
+
this._stateRestored = true;
|
|
279
|
+
console.log('✅ State restored for app:', this.appInfo.id);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
console.error('❌ Failed to restore state:', error);
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
201
287
|
}
|
|
202
288
|
/**
|
|
203
289
|
* Helper: Render HTML into shadow root
|
package/dist/global.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { WeaveBaseApp } from './WeaveBaseApp';
|
|
8
8
|
import { WeaveDOMAPI } from './WeaveDOMAPI';
|
|
9
9
|
import { WeaveAPIClient } from './WeaveAPIClient';
|
|
10
|
+
import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
|
|
10
11
|
declare global {
|
|
11
12
|
interface Window {
|
|
12
13
|
WeaveBaseApp: typeof WeaveBaseApp;
|
|
@@ -14,6 +15,7 @@ declare global {
|
|
|
14
15
|
weaveDOM: WeaveDOMAPI;
|
|
15
16
|
WeaveAPIClient: typeof WeaveAPIClient;
|
|
16
17
|
weaveAPI: WeaveAPIClient;
|
|
18
|
+
WeaveBackgroundAPI: typeof WeaveBackgroundAPI;
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
//# sourceMappingURL=global.d.ts.map
|
package/dist/global.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../src/global.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../src/global.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,YAAY,EAAE,OAAO,YAAY,CAAC;QAClC,WAAW,EAAE,OAAO,WAAW,CAAC;QAChC,QAAQ,EAAE,WAAW,CAAC;QACtB,cAAc,EAAE,OAAO,cAAc,CAAC;QACtC,QAAQ,EAAE,cAAc,CAAC;QACzB,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;KAAG;CACpD"}
|
package/dist/global.js
CHANGED
|
@@ -9,9 +9,11 @@ import { WeaveDOMAPI } from './WeaveDOMAPI';
|
|
|
9
9
|
import weavekDOM from './WeaveDOMAPI';
|
|
10
10
|
import { WeaveAPIClient } from './WeaveAPIClient';
|
|
11
11
|
import weaveAPI from './WeaveAPIClient';
|
|
12
|
+
import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
|
|
12
13
|
// Make SDK available globally
|
|
13
14
|
window.WeaveBaseApp = WeaveBaseApp;
|
|
14
15
|
window.WeaveDOMAPI = WeaveDOMAPI;
|
|
15
16
|
window.weaveDOM = weavekDOM;
|
|
16
17
|
window.WeaveAPIClient = WeaveAPIClient;
|
|
17
18
|
window.weaveAPI = weaveAPI;
|
|
19
|
+
window.WeaveBackgroundAPI = WeaveBackgroundAPI;
|
package/package.json
CHANGED
package/templates/WEAVE_SPEC.md
CHANGED
|
@@ -1083,12 +1083,14 @@ customElements.define('smart-form-assistant', SmartFormAssistant);
|
|
|
1083
1083
|
### Key Takeaways
|
|
1084
1084
|
|
|
1085
1085
|
1. **Single Instance Architecture**: One app instance serves both background and foreground
|
|
1086
|
-
2. **Persistent State**: `this.state` persists across open/close cycles
|
|
1087
|
-
3. **
|
|
1088
|
-
4. **
|
|
1089
|
-
5. **
|
|
1090
|
-
6. **
|
|
1091
|
-
7. **
|
|
1086
|
+
2. **Persistent State**: `this.state` persists across open/close cycles (within session)
|
|
1087
|
+
3. **Page Reload Survival**: Enable `persistState: true` to survive page reloads
|
|
1088
|
+
4. **Multi-Page Flows**: Use `this.background?.setPendingOperation()` for cross-page operations
|
|
1089
|
+
5. **Background Service**: `onBackgroundService()` runs immediately, before DOM attachment
|
|
1090
|
+
6. **URL Monitoring**: `onUrlChange()` detects SPA navigation automatically
|
|
1091
|
+
7. **Shared Context**: Background and foreground share the same instance and state
|
|
1092
|
+
8. **DOM Check**: Use `this.isConnected` to check if app is attached to DOM
|
|
1093
|
+
9. **No User Interaction Required**: Apps can run and inject UI automatically
|
|
1092
1094
|
|
|
1093
1095
|
### Helper Methods (Available in `this`)
|
|
1094
1096
|
|
|
@@ -1102,7 +1104,7 @@ this.query<T>(selector: string): T | null
|
|
|
1102
1104
|
// Query all elements in shadow root
|
|
1103
1105
|
this.queryAll<T>(selector: string): NodeListOf<T>
|
|
1104
1106
|
|
|
1105
|
-
// Update app state
|
|
1107
|
+
// Update app state (auto-persists if persistState: true)
|
|
1106
1108
|
this.setState(updates: object): void
|
|
1107
1109
|
|
|
1108
1110
|
// Access current state
|
|
@@ -1113,8 +1115,263 @@ this.appInfo: WeaveAppInfo
|
|
|
1113
1115
|
|
|
1114
1116
|
// Access shadow root
|
|
1115
1117
|
this.shadowRoot: ShadowRoot
|
|
1118
|
+
|
|
1119
|
+
// Access background API for state persistence
|
|
1120
|
+
this.background: WeaveBackgroundAPI | null
|
|
1116
1121
|
```
|
|
1117
1122
|
|
|
1123
|
+
## State Persistence (Survive Page Reloads)
|
|
1124
|
+
|
|
1125
|
+
### Overview
|
|
1126
|
+
|
|
1127
|
+
Apps can persist state across **page reloads** using the background state service. This is different from `weaveAPI.appData` which persists to the backend database - background state is stored in the browser extension's memory and survives page navigations within the same browser session.
|
|
1128
|
+
|
|
1129
|
+
**Use Cases:**
|
|
1130
|
+
- 🔄 **Survive page reloads** - State persists when user refreshes the page
|
|
1131
|
+
- 📋 **Multi-page flows** - Extract data on page A, fill form on page B
|
|
1132
|
+
- 🔀 **Handle redirects** - Continue operation after OAuth redirect or form submission
|
|
1133
|
+
- 💾 **Temporary state** - Store data that doesn't need long-term persistence
|
|
1134
|
+
|
|
1135
|
+
### Auto-Persist (Recommended)
|
|
1136
|
+
|
|
1137
|
+
The simplest approach is to enable `persistState: true` in your app config. State is automatically:
|
|
1138
|
+
- **Saved** on every `setState()` call (debounced)
|
|
1139
|
+
- **Restored** when the app initializes after a page reload
|
|
1140
|
+
|
|
1141
|
+
```typescript
|
|
1142
|
+
class PersistentCounterApp extends WeaveBaseApp {
|
|
1143
|
+
constructor() {
|
|
1144
|
+
super({
|
|
1145
|
+
id: 'persistent-counter',
|
|
1146
|
+
name: 'Persistent Counter',
|
|
1147
|
+
version: '1.0.0',
|
|
1148
|
+
category: 'utility',
|
|
1149
|
+
description: 'Counter that survives page reloads',
|
|
1150
|
+
author: 'Your Name',
|
|
1151
|
+
|
|
1152
|
+
// ✅ Enable auto-persist
|
|
1153
|
+
persistState: true,
|
|
1154
|
+
|
|
1155
|
+
// Optional: debounce delay in ms (default: 100)
|
|
1156
|
+
persistDebounce: 100,
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
this.state = {
|
|
1160
|
+
count: 0,
|
|
1161
|
+
lastUpdated: null
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
async onBackgroundService() {
|
|
1166
|
+
// ✅ State is already restored automatically!
|
|
1167
|
+
console.log('Restored count:', this.state.count);
|
|
1168
|
+
console.log('Last updated:', this.state.lastUpdated);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
handleIncrement() {
|
|
1172
|
+
// ✅ Automatically saved to background service
|
|
1173
|
+
this.setState({
|
|
1174
|
+
count: this.state.count + 1,
|
|
1175
|
+
lastUpdated: new Date().toISOString()
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
if (this.isConnected) {
|
|
1179
|
+
this.render();
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
render() {
|
|
1184
|
+
this.renderHTML(`
|
|
1185
|
+
<div class="p-4">
|
|
1186
|
+
<p>Count: <strong>${this.state.count}</strong></p>
|
|
1187
|
+
<p>Last updated: ${this.state.lastUpdated || 'Never'}</p>
|
|
1188
|
+
<button id="increment" class="bg-blue-500 text-white px-4 py-2 rounded">
|
|
1189
|
+
Increment
|
|
1190
|
+
</button>
|
|
1191
|
+
<p class="text-gray-500 text-sm mt-4">
|
|
1192
|
+
Refresh the page - count will persist!
|
|
1193
|
+
</p>
|
|
1194
|
+
</div>
|
|
1195
|
+
`);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
setupEventListeners() {
|
|
1199
|
+
this.query('#increment')?.addEventListener('click', () => {
|
|
1200
|
+
this.handleIncrement();
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
customElements.define('persistent-counter', PersistentCounterApp);
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
### Manual State Management
|
|
1209
|
+
|
|
1210
|
+
For more control, use the `this.background` API directly:
|
|
1211
|
+
|
|
1212
|
+
```typescript
|
|
1213
|
+
class ManualStateApp extends WeaveBaseApp {
|
|
1214
|
+
constructor() {
|
|
1215
|
+
super({
|
|
1216
|
+
id: 'manual-state-app',
|
|
1217
|
+
name: 'Manual State App',
|
|
1218
|
+
version: '1.0.0',
|
|
1219
|
+
category: 'utility',
|
|
1220
|
+
description: 'Manual state management',
|
|
1221
|
+
author: 'Your Name',
|
|
1222
|
+
// Note: persistState is false (default)
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
this.state = { data: null };
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
async saveImportantData(data: any) {
|
|
1229
|
+
// Manually save state
|
|
1230
|
+
await this.background?.saveState({ importantData: data });
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async loadSavedData() {
|
|
1234
|
+
// Manually load state
|
|
1235
|
+
const saved = await this.background?.loadState();
|
|
1236
|
+
if (saved?.importantData) {
|
|
1237
|
+
this.state.data = saved.importantData;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
async clearSavedData() {
|
|
1242
|
+
// Clear saved state
|
|
1243
|
+
await this.background?.clearState();
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
### Multi-Page Flows (Pending Operations)
|
|
1249
|
+
|
|
1250
|
+
For operations that span multiple pages, use **pending operations**. This is perfect for:
|
|
1251
|
+
- Extract data on page A → Fill form on page B
|
|
1252
|
+
- Click button → Handle redirect → Continue operation
|
|
1253
|
+
- OAuth flows → Return and continue
|
|
1254
|
+
|
|
1255
|
+
```typescript
|
|
1256
|
+
class DataTransferApp extends WeaveBaseApp {
|
|
1257
|
+
constructor() {
|
|
1258
|
+
super({
|
|
1259
|
+
id: 'data-transfer-app',
|
|
1260
|
+
name: 'Data Transfer App',
|
|
1261
|
+
version: '1.0.0',
|
|
1262
|
+
category: 'integration',
|
|
1263
|
+
description: 'Transfer data between pages',
|
|
1264
|
+
author: 'Your Name',
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
this.state = {
|
|
1268
|
+
extractedContent: null,
|
|
1269
|
+
transferInProgress: false
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
async onBackgroundService() {
|
|
1274
|
+
// Check for pending operation after page loads
|
|
1275
|
+
const pending = await this.background?.getPendingOperation();
|
|
1276
|
+
|
|
1277
|
+
if (pending?.type === 'fill-form') {
|
|
1278
|
+
const pageUrl = await window.weaveDOM.getPageUrl();
|
|
1279
|
+
|
|
1280
|
+
// Verify we're on the target page
|
|
1281
|
+
if (pageUrl.includes(pending.targetUrl || '')) {
|
|
1282
|
+
console.log('✅ On target page, filling form...');
|
|
1283
|
+
await this.fillFormWithData(pending.data);
|
|
1284
|
+
await this.background?.clearPendingOperation();
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
async handleTransferClick() {
|
|
1290
|
+
if (!this.state.extractedContent) {
|
|
1291
|
+
console.error('No content to transfer');
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Save pending operation BEFORE navigation
|
|
1296
|
+
await this.background?.setPendingOperation({
|
|
1297
|
+
type: 'fill-form',
|
|
1298
|
+
data: {
|
|
1299
|
+
content: this.state.extractedContent,
|
|
1300
|
+
timestamp: Date.now()
|
|
1301
|
+
},
|
|
1302
|
+
targetUrl: 'target-system.com/form'
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
// Navigate to target page
|
|
1306
|
+
window.open('https://target-system.com/form', '_self');
|
|
1307
|
+
// Page will reload, but pending operation is saved!
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
async fillFormWithData(data: any) {
|
|
1311
|
+
// Fill form fields with transferred data
|
|
1312
|
+
await window.weaveDOM.setFormFieldValue('#notes', data.content);
|
|
1313
|
+
console.log('✅ Form filled with transferred data');
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
customElements.define('data-transfer-app', DataTransferApp);
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
### Pending Operation Structure
|
|
1321
|
+
|
|
1322
|
+
```typescript
|
|
1323
|
+
interface PendingOperation {
|
|
1324
|
+
type: string; // Operation type (e.g., 'fill-form', 'continue-auth')
|
|
1325
|
+
data: any; // Operation-specific data
|
|
1326
|
+
targetUrl?: string; // Optional: URL where operation should resume
|
|
1327
|
+
}
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
### Background API Reference
|
|
1331
|
+
|
|
1332
|
+
```typescript
|
|
1333
|
+
// Available via this.background
|
|
1334
|
+
|
|
1335
|
+
// Save state (survives page reload)
|
|
1336
|
+
await this.background?.saveState(state: any): Promise<void>
|
|
1337
|
+
|
|
1338
|
+
// Load state
|
|
1339
|
+
await this.background?.loadState<T>(): Promise<T | null>
|
|
1340
|
+
|
|
1341
|
+
// Clear state
|
|
1342
|
+
await this.background?.clearState(): Promise<void>
|
|
1343
|
+
|
|
1344
|
+
// Set pending operation (for multi-page flows)
|
|
1345
|
+
await this.background?.setPendingOperation(op: PendingOperation): Promise<void>
|
|
1346
|
+
|
|
1347
|
+
// Get pending operation
|
|
1348
|
+
await this.background?.getPendingOperation(): Promise<PendingOperation | null>
|
|
1349
|
+
|
|
1350
|
+
// Clear pending operation
|
|
1351
|
+
await this.background?.clearPendingOperation(): Promise<void>
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
### State TTL (Time To Live)
|
|
1355
|
+
|
|
1356
|
+
Persisted state expires after **5 minutes** by default. This prevents stale state from accumulating.
|
|
1357
|
+
|
|
1358
|
+
For long-term persistence, use `this.weaveAPI.appData.*` methods instead.
|
|
1359
|
+
|
|
1360
|
+
### Best Practices
|
|
1361
|
+
|
|
1362
|
+
#### ✅ DO:
|
|
1363
|
+
- Use `persistState: true` for simple state persistence
|
|
1364
|
+
- Use pending operations for multi-page flows
|
|
1365
|
+
- Clear pending operations after handling them
|
|
1366
|
+
- Check `this.background` exists before calling methods
|
|
1367
|
+
- Use background state for temporary/session data
|
|
1368
|
+
|
|
1369
|
+
#### ❌ DON'T:
|
|
1370
|
+
- Don't use background state for long-term data (use `weaveAPI.appData`)
|
|
1371
|
+
- Don't store sensitive data in background state
|
|
1372
|
+
- Don't forget to clear pending operations
|
|
1373
|
+
- Don't assume state will persist forever (5 min TTL)
|
|
1374
|
+
|
|
1118
1375
|
## Weave Backend API
|
|
1119
1376
|
|
|
1120
1377
|
### ⚠️ CRITICAL: API Access Restrictions
|