@weave-apps/sdk 0.9.0 → 0.11.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.
Files changed (37) hide show
  1. package/dist/WeaveAPIClient.d.ts +49 -0
  2. package/dist/WeaveAPIClient.d.ts.map +1 -1
  3. package/dist/WeaveDOMAPI.d.ts +66 -0
  4. package/dist/WeaveDOMAPI.d.ts.map +1 -1
  5. package/dist/WeaveDOMAPI.js +51 -0
  6. package/dist/apis/api-mono/services/interfaces/WorkflowsTriggersWorkflowCompanyIdPostPost.d.ts +224 -0
  7. package/dist/apis/api-mono/services/interfaces/WorkflowsTriggersWorkflowCompanyIdPostPost.d.ts.map +1 -0
  8. package/dist/apis/api-mono/services/interfaces/WorkflowsTriggersWorkflowCompanyIdPostPost.js +66 -0
  9. package/dist/app-sdk/src/WeaveAPIClient.d.ts +370 -0
  10. package/dist/app-sdk/src/WeaveAPIClient.d.ts.map +1 -0
  11. package/dist/app-sdk/src/WeaveAPIClient.js +361 -0
  12. package/dist/app-sdk/src/WeaveAppInstanceAPI.d.ts +237 -0
  13. package/dist/app-sdk/src/WeaveAppInstanceAPI.d.ts.map +1 -0
  14. package/dist/app-sdk/src/WeaveAppInstanceAPI.js +395 -0
  15. package/dist/app-sdk/src/WeaveBackgroundAPI.d.ts +81 -0
  16. package/dist/app-sdk/src/WeaveBackgroundAPI.d.ts.map +1 -0
  17. package/dist/app-sdk/src/WeaveBackgroundAPI.js +165 -0
  18. package/dist/app-sdk/src/WeaveBaseApp.d.ts +318 -0
  19. package/dist/app-sdk/src/WeaveBaseApp.d.ts.map +1 -0
  20. package/dist/app-sdk/src/WeaveBaseApp.js +434 -0
  21. package/dist/app-sdk/src/WeaveCronAPI.d.ts +68 -0
  22. package/dist/app-sdk/src/WeaveCronAPI.d.ts.map +1 -0
  23. package/dist/app-sdk/src/WeaveCronAPI.js +172 -0
  24. package/dist/app-sdk/src/WeaveDOMAPI.d.ts +593 -0
  25. package/dist/app-sdk/src/WeaveDOMAPI.d.ts.map +1 -0
  26. package/dist/app-sdk/src/WeaveDOMAPI.js +774 -0
  27. package/dist/app-sdk/src/global.d.ts +25 -0
  28. package/dist/app-sdk/src/global.d.ts.map +1 -0
  29. package/dist/app-sdk/src/global.js +23 -0
  30. package/dist/app-sdk/src/index.d.ts +14 -0
  31. package/dist/app-sdk/src/index.d.ts.map +1 -0
  32. package/dist/app-sdk/src/index.js +14 -0
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -1
  36. package/package.json +3 -3
  37. package/templates/WEAVE_SPEC.md +417 -1
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Global SDK Loader
3
+ *
4
+ * Makes the Weave SDK available globally for third-party apps
5
+ * that are loaded as JavaScript strings (not ES6 modules)
6
+ */
7
+ import { WeaveBaseApp } from './WeaveBaseApp';
8
+ import { WeaveDOMAPI } from './WeaveDOMAPI';
9
+ import { WeaveAPIClient } from './WeaveAPIClient';
10
+ import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
11
+ import { WeaveCronAPI } from './WeaveCronAPI';
12
+ import { WeaveAppInstanceAPI } from './WeaveAppInstanceAPI';
13
+ declare global {
14
+ interface Window {
15
+ WeaveBaseApp: typeof WeaveBaseApp;
16
+ WeaveDOMAPI: typeof WeaveDOMAPI;
17
+ weaveDOM: WeaveDOMAPI;
18
+ WeaveAPIClient: typeof WeaveAPIClient;
19
+ weaveAPI: WeaveAPIClient;
20
+ WeaveBackgroundAPI: typeof WeaveBackgroundAPI;
21
+ WeaveCronAPI: typeof WeaveCronAPI;
22
+ WeaveAppInstanceAPI: typeof WeaveAppInstanceAPI;
23
+ }
24
+ }
25
+ //# sourceMappingURL=global.d.ts.map
@@ -0,0 +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;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG5D,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;QAC9C,YAAY,EAAE,OAAO,YAAY,CAAC;QAClC,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;KAAG;CACtD"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Global SDK Loader
3
+ *
4
+ * Makes the Weave SDK available globally for third-party apps
5
+ * that are loaded as JavaScript strings (not ES6 modules)
6
+ */
7
+ import { WeaveBaseApp } from './WeaveBaseApp';
8
+ import { WeaveDOMAPI } from './WeaveDOMAPI';
9
+ import weavekDOM from './WeaveDOMAPI';
10
+ import { WeaveAPIClient } from './WeaveAPIClient';
11
+ import weaveAPI from './WeaveAPIClient';
12
+ import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
13
+ import { WeaveCronAPI } from './WeaveCronAPI';
14
+ import { WeaveAppInstanceAPI } from './WeaveAppInstanceAPI';
15
+ // Make SDK available globally
16
+ window.WeaveBaseApp = WeaveBaseApp;
17
+ window.WeaveDOMAPI = WeaveDOMAPI;
18
+ window.weaveDOM = weavekDOM;
19
+ window.WeaveAPIClient = WeaveAPIClient;
20
+ window.weaveAPI = weaveAPI;
21
+ window.WeaveBackgroundAPI = WeaveBackgroundAPI;
22
+ window.WeaveCronAPI = WeaveCronAPI;
23
+ window.WeaveAppInstanceAPI = WeaveAppInstanceAPI;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Weave App SDK
3
+ *
4
+ * Main exports for third-party app development
5
+ */
6
+ export { WeaveBaseApp, type WeaveAppInfo, type PendingOperation } from './WeaveBaseApp';
7
+ export { WeaveDOMAPI, type ElementSnapshot, type InsertPosition } from './WeaveDOMAPI';
8
+ export { default as weaveDOM } from './WeaveDOMAPI';
9
+ export { WeaveAPIClient, type AIChatRequest, type AIChatResponse, type AppData, type CreateAppDataRequest, type UpdateAppDataRequest, type FormSubmissionFieldOption, type FormSubmissionCondition, type FormSubmissionResponseEntry, type FormSubmissionDefinitionSection, type FormSubmissionDefinitionField, type FormSubmissionDefinitionPayload, type FormSubmissionDataPayload, } from './WeaveAPIClient';
10
+ export { default as weaveAPI } from './WeaveAPIClient';
11
+ export { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
12
+ export { WeaveCronAPI } from './WeaveCronAPI';
13
+ import './global';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,+BAA+B,EACpC,KAAK,6BAA6B,EAClC,KAAK,+BAA+B,EACpC,KAAK,yBAAyB,GAC/B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,UAAU,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Weave App SDK
3
+ *
4
+ * Main exports for third-party app development
5
+ */
6
+ export { WeaveBaseApp } from './WeaveBaseApp';
7
+ export { WeaveDOMAPI } from './WeaveDOMAPI';
8
+ export { default as weaveDOM } from './WeaveDOMAPI';
9
+ export { WeaveAPIClient, } from './WeaveAPIClient';
10
+ export { default as weaveAPI } from './WeaveAPIClient';
11
+ export { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
12
+ export { WeaveCronAPI } from './WeaveCronAPI';
13
+ // Import global.ts to ensure Window interface augmentation is included
14
+ import './global';
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  export { WeaveBaseApp, type WeaveAppInfo, type PendingOperation } from './WeaveBaseApp';
7
7
  export { WeaveDOMAPI, type ElementSnapshot, type InsertPosition } from './WeaveDOMAPI';
8
8
  export { default as weaveDOM } from './WeaveDOMAPI';
9
- export { WeaveAPIClient, type AIChatRequest, type AIChatResponse, type AppData, type CreateAppDataRequest, type UpdateAppDataRequest } from './WeaveAPIClient';
9
+ export { WeaveAPIClient, type AIChatRequest, type AIChatResponse, type AppData, type CreateAppDataRequest, type UpdateAppDataRequest, type FormSubmissionFieldOption, type FormSubmissionCondition, type FormSubmissionResponseEntry, type FormSubmissionDefinitionSection, type FormSubmissionDefinitionField, type FormSubmissionDefinitionPayload, type FormSubmissionDataPayload, } from './WeaveAPIClient';
10
10
  export { default as weaveAPI } from './WeaveAPIClient';
11
11
  export { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
12
12
  export { WeaveCronAPI } from './WeaveCronAPI';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,+BAA+B,EACpC,KAAK,6BAA6B,EAClC,KAAK,+BAA+B,EACpC,KAAK,yBAAyB,GAC/B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  export { WeaveBaseApp } from './WeaveBaseApp';
7
7
  export { WeaveDOMAPI } from './WeaveDOMAPI';
8
8
  export { default as weaveDOM } from './WeaveDOMAPI';
9
- export { WeaveAPIClient } from './WeaveAPIClient';
9
+ export { WeaveAPIClient, } from './WeaveAPIClient';
10
10
  export { default as weaveAPI } from './WeaveAPIClient';
11
11
  export { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
12
12
  export { WeaveCronAPI } from './WeaveCronAPI';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weave-apps/sdk",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "SDK for building Weave Micro Apps",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -32,11 +32,11 @@
32
32
  "author": "Weave Platform",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "esbuild": "^0.24.0",
35
+ "esbuild": "^0.28.0",
36
36
  "typescript": "^5.9.3"
37
37
  },
38
38
  "devDependencies": {
39
- "@types/node": "^20.0.0"
39
+ "@types/node": "^22.0.0"
40
40
  },
41
41
  "files": [
42
42
  "dist",
@@ -7,15 +7,18 @@
7
7
 
8
8
  ## Architecture Overview
9
9
 
10
- Weave apps are **Web Components** that run in an **iframe** inside a browser extension sidebar. They interact with two secure APIs:
10
+ Weave apps are **Web Components** that run in an **iframe** inside a browser extension sidebar. They interact with three secure APIs:
11
11
 
12
12
  1. **Weave Backend API** - AI services, app data, forms, and company member discovery
13
13
  2. **DOM Bridge API** - Parent page DOM manipulation
14
+ 3. **Browser Extension API** - Cross-tab coordination, tab focus, state persistence via background service worker
14
15
 
15
16
  ```
16
17
  [Weave Backend] ←→ [Sidebar: API Bridge] ←→ [Iframe: Weave App]
17
18
 
18
19
  [Parent Page] ←→ [Content Script: DOMBridge] ←→ [Iframe: Weave App]
20
+
21
+ [Background SW] ←→ [Content Script: StateBridge] ←→ [Iframe: Weave App]
19
22
  ```
20
23
 
21
24
  ### Key Constraints
@@ -1789,6 +1792,407 @@ class MyApp extends WeaveBaseApp {
1789
1792
  }
1790
1793
  ```
1791
1794
 
1795
+ ## Cross-Tab Instance Management
1796
+
1797
+ ### Overview
1798
+
1799
+ When the same app runs in **multiple browser tabs** (e.g., user opens the same site in 3 tabs), each tab gets its own app instance. The **Instance API** (`this.instances`) lets these instances discover each other, elect a "controller" tab, and exchange messages — all through the browser extension's background service worker.
1800
+
1801
+ **Use cases:**
1802
+ - 🎮 **Controller election** — Only one tab runs automation; others observe
1803
+ - 📡 **Cross-tab messaging** — Broadcast status, sync state, or coordinate work
1804
+ - 🔄 **Failover** — Detect when the controller tab closes and elect a new one
1805
+ - 🚫 **Deduplication** — Prevent duplicate scrapes, API calls, or UI injections
1806
+
1807
+ ### Architecture
1808
+
1809
+ ```
1810
+ Tab 1 (iframe) ──┐
1811
+ │ window.parent.postMessage (APP_*)
1812
+ Tab 2 (iframe) ──┼──► Content Script (BackgroundStateBridge)
1813
+ │ ──► chrome.runtime.sendMessage
1814
+ Tab 3 (iframe) ──┘ ──► Background: AppInstanceRegistry
1815
+
1816
+ ├── Tracks all instances per appId
1817
+ ├── Manages controller status
1818
+ └── Routes messages between tabs
1819
+ ```
1820
+
1821
+ ### Registering an Instance
1822
+
1823
+ Call `this.instances.register()` in `onBackgroundService()` to make this tab discoverable:
1824
+
1825
+ ```typescript
1826
+ class MyApp extends WeaveBaseApp {
1827
+ async onBackgroundService() {
1828
+ const pageUrl = await window.weaveDOM.getPageUrl();
1829
+
1830
+ // Register this instance with metadata
1831
+ const allInstances = await this.instances.register({
1832
+ url: pageUrl,
1833
+ startedAt: Date.now(),
1834
+ });
1835
+
1836
+ console.log(`Total instances: ${allInstances.length}`);
1837
+ }
1838
+ }
1839
+ ```
1840
+
1841
+ ### Controller Election
1842
+
1843
+ Only **one instance** can be controller at a time. Use this to decide which tab runs automations:
1844
+
1845
+ ```typescript
1846
+ // Try to become controller
1847
+ const claimed = await this.instances.claimController();
1848
+ if (claimed) {
1849
+ console.log('This tab is the controller');
1850
+ this.startAutomation();
1851
+ } else {
1852
+ console.log('Another tab is already the controller');
1853
+ }
1854
+
1855
+ // Check current controller
1856
+ const controller = await this.instances.getController();
1857
+ // Returns: { appId, tabId, url, isController, registeredAt, metadata } or null
1858
+
1859
+ // Release controller (e.g., before page unload)
1860
+ await this.instances.releaseController();
1861
+
1862
+ // Force-claim controller (takes over from current controller)
1863
+ await this.instances.claimController(true);
1864
+ ```
1865
+
1866
+ ### Cross-Tab Messaging
1867
+
1868
+ Send messages between instances of the same app:
1869
+
1870
+ ```typescript
1871
+ // Broadcast to ALL other instances
1872
+ await this.instances.broadcast({
1873
+ type: 'WORKFLOW_STARTED',
1874
+ data: { workflowName: 'daily-sync' },
1875
+ });
1876
+
1877
+ // Send to the controller instance only
1878
+ await this.instances.sendToController({
1879
+ type: 'REQUEST_STATUS',
1880
+ });
1881
+
1882
+ // Send to a specific tab
1883
+ await this.instances.sendToTab(123, {
1884
+ type: 'PING',
1885
+ });
1886
+ ```
1887
+
1888
+ ### Listening for Messages & Changes
1889
+
1890
+ ```typescript
1891
+ // Listen for messages from other instances
1892
+ this.instances.onMessage((msg) => {
1893
+ switch (msg.type) {
1894
+ case 'WORKFLOW_STARTED':
1895
+ console.log('Another tab started a workflow');
1896
+ break;
1897
+ case 'YIELD_CONTROL':
1898
+ this.instances.releaseController();
1899
+ break;
1900
+ }
1901
+ });
1902
+
1903
+ // Listen for instance changes (tab opened/closed)
1904
+ this.instances.onInstanceChange((instances) => {
1905
+ console.log(`Now ${instances.length} instances`);
1906
+
1907
+ // Check if controller is still alive
1908
+ const controller = instances.find((i) => i.isController);
1909
+ if (!controller) {
1910
+ // Controller tab was closed — claim control
1911
+ this.instances.claimController();
1912
+ }
1913
+ });
1914
+ ```
1915
+
1916
+ ### Instance API Reference
1917
+
1918
+ ```typescript
1919
+ // Available via this.instances (WeaveAppInstanceAPI)
1920
+
1921
+ // Register this tab — makes it discoverable
1922
+ await this.instances.register(metadata?: Record<string, any>): Promise<AppInstance[]>
1923
+
1924
+ // Unregister this tab
1925
+ await this.instances.unregister(): Promise<void>
1926
+
1927
+ // Get all instances of this app
1928
+ await this.instances.getInstances(): Promise<AppInstance[]>
1929
+
1930
+ // Claim controller status (only one tab can be controller)
1931
+ await this.instances.claimController(force?: boolean): Promise<boolean>
1932
+
1933
+ // Release controller status
1934
+ await this.instances.releaseController(): Promise<void>
1935
+
1936
+ // Check if this tab is the controller
1937
+ await this.instances.isController(): Promise<boolean>
1938
+
1939
+ // Get the current controller instance (or null)
1940
+ await this.instances.getController(): Promise<AppInstance | null>
1941
+
1942
+ // Broadcast to all other instances of this app
1943
+ await this.instances.broadcast(message: { type: string; data?: any }): Promise<number[]>
1944
+
1945
+ // Send to the controller instance
1946
+ await this.instances.sendToController(message: { type: string; data?: any }): Promise<number | null>
1947
+
1948
+ // Send to a specific tab
1949
+ await this.instances.sendToTab(tabId: number, message: { type: string; data?: any }): Promise<boolean>
1950
+
1951
+ // Listen for messages from other instances (returns unsubscribe function)
1952
+ this.instances.onMessage(callback: (msg: InstanceMessage) => void): () => void
1953
+
1954
+ // Listen for instance list changes (returns unsubscribe function)
1955
+ this.instances.onInstanceChange(callback: (instances: AppInstance[]) => void): () => void
1956
+
1957
+ // This tab's ID (available after register())
1958
+ this.instances.tabId: number | null
1959
+ ```
1960
+
1961
+ ### AppInstance Structure
1962
+
1963
+ ```typescript
1964
+ interface AppInstance {
1965
+ appId: string; // App identifier
1966
+ tabId: number; // Chrome tab ID
1967
+ url: string; // Page URL when registered
1968
+ isController: boolean; // Whether this instance is the controller
1969
+ registeredAt: number; // Timestamp of registration
1970
+ metadata?: Record<string, any>; // Custom data passed during register()
1971
+ }
1972
+ ```
1973
+
1974
+ ### Best Practices
1975
+
1976
+ #### ✅ DO:
1977
+ - Register in `onBackgroundService()` — this runs on every page load/reload
1978
+ - Use broadcast-based election (ask "who is controller?" before claiming)
1979
+ - Add jitter (random delay) when multiple tabs start simultaneously
1980
+ - Remove overlays/banners when yielding controller status
1981
+ - Handle `onInstanceChange` to detect when the controller tab closes
1982
+
1983
+ #### ❌ DON'T:
1984
+ - Don't trust persisted `isController` state — always re-verify via the API or broadcast
1985
+ - Don't call `claimController()` without first checking if a live controller exists
1986
+ - Don't assume messages arrive in order — tabs may start at different times
1987
+
1988
+ ### Complete Example: Single-Controller Automation
1989
+
1990
+ ```typescript
1991
+ class AutoSyncApp extends WeaveBaseApp {
1992
+ constructor() {
1993
+ super({
1994
+ id: 'auto-sync',
1995
+ name: 'Auto Sync',
1996
+ version: '1.0.0',
1997
+ category: 'automation',
1998
+ description: 'Syncs data on a schedule — runs in only one tab',
1999
+ author: 'Weave Team',
2000
+ persistState: true,
2001
+ });
2002
+
2003
+ this.state = { isController: false, syncCount: 0 };
2004
+ }
2005
+
2006
+ async onBackgroundService() {
2007
+ // Reset controller state — only register() may set it to true
2008
+ this.setState({ isController: false });
2009
+
2010
+ await this.instances.register({ url: await window.weaveDOM.getPageUrl() });
2011
+
2012
+ // Listen for messages
2013
+ this.instances.onMessage((msg) => {
2014
+ if (msg.type === 'I_AM_CONTROLLER' && this.state.isController) {
2015
+ // Another tab is also controller — yield to avoid duplicates
2016
+ this.instances.releaseController();
2017
+ this.setState({ isController: false });
2018
+ }
2019
+ });
2020
+
2021
+ // Listen for instance changes (e.g., controller tab closed)
2022
+ this.instances.onInstanceChange((instances) => {
2023
+ const controller = instances.find((i) => i.isController);
2024
+ if (!controller) {
2025
+ this.tryClaimController();
2026
+ }
2027
+ });
2028
+
2029
+ // Try to become controller
2030
+ await this.tryClaimController();
2031
+
2032
+ if (this.state.isController) {
2033
+ await this.cronTab('0 * * * * *', () => this.runSync(), 'sync');
2034
+ }
2035
+ }
2036
+
2037
+ private async tryClaimController(): Promise<void> {
2038
+ // Ask if anyone is already controller
2039
+ this.instances.broadcast({ type: 'WHO_IS_CONTROLLER' });
2040
+
2041
+ // Wait briefly for a response
2042
+ await new Promise((r) => setTimeout(r, 2000));
2043
+
2044
+ // If no one answered, claim
2045
+ const claimed = await this.instances.claimController();
2046
+ if (claimed) {
2047
+ this.setState({ isController: true });
2048
+ this.instances.broadcast({ type: 'I_AM_CONTROLLER' });
2049
+ }
2050
+ }
2051
+
2052
+ private runSync(): void {
2053
+ if (!this.state.isController) return;
2054
+ this.setState({ syncCount: this.state.syncCount + 1 });
2055
+ console.log(`Sync #${this.state.syncCount}`);
2056
+ }
2057
+ }
2058
+
2059
+ customElements.define('auto-sync', AutoSyncApp);
2060
+ ```
2061
+
2062
+ ## Browser Extension Messaging (Advanced)
2063
+
2064
+ ### Overview
2065
+
2066
+ Apps run inside an **iframe** within the browser extension's sidebar. The iframe cannot directly access Chrome extension APIs. However, apps can send messages to the **background service worker** via the content script relay. This unlocks capabilities that are impossible from within an iframe alone.
2067
+
2068
+ ### Message Flow
2069
+
2070
+ ```
2071
+ App (sidebar iframe)
2072
+ │ window.parent.postMessage({ type: 'BG_*', requestId, payload })
2073
+
2074
+ Content Script (BackgroundStateBridge)
2075
+ │ Catches any message with type starting with 'BG_'
2076
+ │ Forwards via chrome.runtime.sendMessage
2077
+
2078
+ Background Service Worker (MessageHandler)
2079
+ │ Processes the message (has full chrome.* API access)
2080
+ │ Returns response
2081
+
2082
+ Content Script
2083
+ │ Wraps response in BG_STATE_RESPONSE
2084
+ │ Posts back via window.postMessage
2085
+
2086
+ App (sidebar iframe)
2087
+ │ Matches response by requestId
2088
+ ```
2089
+
2090
+ ### How to Send a Message
2091
+
2092
+ All `BG_*` prefixed messages are automatically relayed by the `BackgroundStateBridge` content script. Your app sends via `window.parent.postMessage`:
2093
+
2094
+ ```typescript
2095
+ window.parent.postMessage(
2096
+ {
2097
+ type: 'BG_FOCUS_TAB', // Must start with 'BG_'
2098
+ requestId: `my-request-${Date.now()}`, // Unique ID for response matching
2099
+ payload: { appId: 'my-app-id' }, // Must include appId
2100
+ },
2101
+ '*',
2102
+ );
2103
+ ```
2104
+
2105
+ **Required fields:**
2106
+ - **`type`** — Must start with `BG_` to be caught by the relay
2107
+ - **`requestId`** — Unique string for matching the response
2108
+ - **`payload.appId`** — Your app's ID (required by the bridge)
2109
+
2110
+ ### Available Background Messages
2111
+
2112
+ #### `BG_FOCUS_TAB` — Focus This Browser Tab
2113
+
2114
+ Brings the current tab **and its browser window** to the foreground. Essential when the app needs to grab the user's attention (e.g., prompting them to log in).
2115
+
2116
+ ```typescript
2117
+ // Focus this tab (bring to foreground, even from another window)
2118
+ window.parent.postMessage(
2119
+ {
2120
+ type: 'BG_FOCUS_TAB',
2121
+ requestId: `focus-${Date.now()}`,
2122
+ payload: { appId: 'my-app-id' },
2123
+ },
2124
+ '*',
2125
+ );
2126
+ ```
2127
+
2128
+ **What happens in the background:**
2129
+ ```
2130
+ chrome.tabs.update(tabId, { active: true }); // Activate the tab
2131
+ chrome.windows.update(windowId, { focused: true }); // Focus the window
2132
+ ```
2133
+
2134
+ **Use cases:**
2135
+ - 🔐 User session expired → focus the login tab so they notice
2136
+ - ⚠️ Automation needs attention → bring the tab to the foreground
2137
+ - 🔔 Critical notification → ensure the user sees it
2138
+
2139
+ #### `BG_SAVE_STATE` / `BG_LOAD_STATE` / `BG_CLEAR_STATE`
2140
+
2141
+ These are used internally by the `persistState: true` feature and `this.background` API. You normally don't call these directly — use `this.setState()` or `this.background?.saveState()` instead.
2142
+
2143
+ #### `BG_SET_PENDING_OP` / `BG_GET_PENDING_OP` / `BG_CLEAR_PENDING_OP`
2144
+
2145
+ Used internally by `this.background?.setPendingOperation()`. You normally don't call these directly.
2146
+
2147
+ ### Example: Focus Tab on Login Page
2148
+
2149
+ ```typescript
2150
+ class MyApp extends WeaveBaseApp {
2151
+ async onBackgroundService() {
2152
+ const loginPage = await this.detectLoginPage();
2153
+
2154
+ if (loginPage && this.state.isController) {
2155
+ // Focus the tab so the user sees they need to log in
2156
+ window.parent.postMessage(
2157
+ {
2158
+ type: 'BG_FOCUS_TAB',
2159
+ requestId: `focus-${Date.now()}`,
2160
+ payload: { appId: 'my-app-id' },
2161
+ },
2162
+ '*',
2163
+ );
2164
+
2165
+ // Also inject a visual prompt
2166
+ await window.weaveDOM.injectElement(
2167
+ 'body',
2168
+ 'beforeend',
2169
+ '<div style="position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:999999;background:#dc2626;color:white;padding:14px 28px;border-radius:8px;font-family:system-ui;font-weight:600;pointer-events:none;">⚠️ Please log in to resume automatic sync</div>',
2170
+ { elementId: 'login-notice' },
2171
+ );
2172
+ }
2173
+ }
2174
+
2175
+ private async detectLoginPage(): Promise<boolean> {
2176
+ const loginEl = await window.weaveDOM.query('.login-page');
2177
+ return !!loginEl;
2178
+ }
2179
+ }
2180
+ ```
2181
+
2182
+ ### Best Practices
2183
+
2184
+ #### ✅ DO:
2185
+ - Always include a unique `requestId` and `payload.appId`
2186
+ - Use `BG_FOCUS_TAB` sparingly — only when the user genuinely needs to act
2187
+ - Wrap in try/catch in case the content script relay isn't available
2188
+ - Prefer SDK methods (`this.background`, `this.instances`) over raw `postMessage` when available
2189
+
2190
+ #### ❌ DON'T:
2191
+ - Don't send messages without the `BG_` prefix — they won't be relayed
2192
+ - Don't omit `requestId` or `payload.appId` — the bridge silently drops them
2193
+ - Don't spam `BG_FOCUS_TAB` in a loop — focus once, then wait for user action
2194
+ - Don't use raw `postMessage` for things the SDK already provides (state, cron, instances)
2195
+
1792
2196
  ## Weave Backend API
1793
2197
 
1794
2198
  ### ⚠️ CRITICAL: API Access Restrictions
@@ -4418,6 +4822,18 @@ customElements.define('page-title-editor', PageTitleEditor);
4418
4822
  - **Element Injection:** `injectElement`, `removeInjectedElement`
4419
4823
  - **Element Listeners:** `startElementClickListener`, `stopElementClickListener`
4420
4824
 
4825
+ ### Cross-Tab Instance API (`this.instances`)
4826
+ - **Registration:** `register`, `unregister`, `getInstances`
4827
+ - **Controller:** `claimController`, `releaseController`, `isController`, `getController`
4828
+ - **Messaging:** `broadcast`, `sendToController`, `sendToTab`
4829
+ - **Listeners:** `onMessage`, `onInstanceChange`
4830
+ - **Property:** `tabId` (available after `register()`)
4831
+
4832
+ ### Browser Extension Messaging (`window.parent.postMessage`)
4833
+ - **`BG_FOCUS_TAB`** — Focus this tab and its browser window (requires `requestId` + `payload.appId`)
4834
+ - **`BG_SAVE_STATE`** / **`BG_LOAD_STATE`** / **`BG_CLEAR_STATE`** — State persistence (prefer `this.background`)
4835
+ - **`BG_SET_PENDING_OP`** / **`BG_GET_PENDING_OP`** / **`BG_CLEAR_PENDING_OP`** — Pending ops (prefer `this.background`)
4836
+
4421
4837
  ### Lifecycle
4422
4838
  1. `constructor()` - Initialize
4423
4839
  2. `connectedCallback()` - Added to DOM