@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,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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weave-apps/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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.
|
|
35
|
+
"esbuild": "^0.28.0",
|
|
36
36
|
"typescript": "^5.9.3"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/node": "^
|
|
39
|
+
"@types/node": "^22.0.0"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"dist",
|
package/templates/WEAVE_SPEC.md
CHANGED
|
@@ -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
|
|
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
|
|
@@ -1986,6 +2390,47 @@ class MyApp extends WeaveBaseApp {
|
|
|
1986
2390
|
}
|
|
1987
2391
|
```
|
|
1988
2392
|
|
|
2393
|
+
#### AIChatRequest Interface
|
|
2394
|
+
|
|
2395
|
+
| Property | Type | Required | Description |
|
|
2396
|
+
|----------|------|----------|-------------|
|
|
2397
|
+
| `prompt` | `string` | ✅ | The user's input prompt |
|
|
2398
|
+
| `context` | `string` | ❌ | Optional context/system prompt for the AI |
|
|
2399
|
+
| `disableJsonExtraction` | `boolean` | ❌ | If `true`, returns raw text instead of attempting JSON extraction |
|
|
2400
|
+
| `creativityLevel` | `'precise' \| 'low' \| 'balanced' \| 'high' \| 'creative'` | ❌ | Controls how creative or deterministic the AI response should be |
|
|
2401
|
+
|
|
2402
|
+
#### Creativity Level
|
|
2403
|
+
|
|
2404
|
+
The `creativityLevel` parameter controls the AI's temperature (randomness) using a semantic scale that maps to model-specific values internally:
|
|
2405
|
+
|
|
2406
|
+
| Level | Behaviour | Use when... |
|
|
2407
|
+
|-------|-----------|-------------|
|
|
2408
|
+
| `'precise'` | Fully deterministic, no randomness | Extracting structured data, form filling, JSON output |
|
|
2409
|
+
| `'low'` | Mostly deterministic with slight variation | Summarisation, factual Q&A |
|
|
2410
|
+
| `'balanced'` | Moderate creativity (default when omitted) | General-purpose tasks |
|
|
2411
|
+
| `'high'` | More creative and varied | Writing suggestions, brainstorming |
|
|
2412
|
+
| `'creative'` | Maximum randomness | Free-form creative writing |
|
|
2413
|
+
|
|
2414
|
+
**Example — precise extraction:**
|
|
2415
|
+
```typescript
|
|
2416
|
+
const response = await this.weaveAPI.ai.chat({
|
|
2417
|
+
prompt: `Extract the patient name from: ${notes}`,
|
|
2418
|
+
context: 'Return ONLY the name, no explanation.',
|
|
2419
|
+
creativityLevel: 'precise'
|
|
2420
|
+
});
|
|
2421
|
+
```
|
|
2422
|
+
|
|
2423
|
+
**Example — creative suggestions:**
|
|
2424
|
+
```typescript
|
|
2425
|
+
const response = await this.weaveAPI.ai.chat({
|
|
2426
|
+
prompt: 'Suggest 5 alternative phrasings for this sentence.',
|
|
2427
|
+
context: sentence,
|
|
2428
|
+
creativityLevel: 'creative'
|
|
2429
|
+
});
|
|
2430
|
+
```
|
|
2431
|
+
|
|
2432
|
+
> **Tip:** If you don't specify `creativityLevel`, the API uses a sensible default. Only set it when you need deterministic output (e.g. JSON extraction → `'precise'`) or extra creativity.
|
|
2433
|
+
|
|
1989
2434
|
**Example Use Cases:**
|
|
1990
2435
|
- Summarize page content
|
|
1991
2436
|
- Answer questions about data
|
|
@@ -4392,7 +4837,7 @@ customElements.define('page-title-editor', PageTitleEditor);
|
|
|
4392
4837
|
### Backend API Methods (`this.weaveAPI`)
|
|
4393
4838
|
- **⚠️ ALWAYS use `this.weaveAPI` instead of `window.weaveAPI`**
|
|
4394
4839
|
- **AI Service:**
|
|
4395
|
-
- `this.weaveAPI.ai.chat(request)` - Call Weave AI
|
|
4840
|
+
- `this.weaveAPI.ai.chat(request)` - Call Weave AI (supports `creativityLevel`: `'precise'` | `'low'` | `'balanced'` | `'high'` | `'creative'`)
|
|
4396
4841
|
- **App Data Service:**
|
|
4397
4842
|
- `this.weaveAPI.appData.getAll()` - Get all app data (returns `PaginatedResponse<AppData>`)
|
|
4398
4843
|
- `this.weaveAPI.appData.create(request)` - Create new data
|
|
@@ -4418,6 +4863,18 @@ customElements.define('page-title-editor', PageTitleEditor);
|
|
|
4418
4863
|
- **Element Injection:** `injectElement`, `removeInjectedElement`
|
|
4419
4864
|
- **Element Listeners:** `startElementClickListener`, `stopElementClickListener`
|
|
4420
4865
|
|
|
4866
|
+
### Cross-Tab Instance API (`this.instances`)
|
|
4867
|
+
- **Registration:** `register`, `unregister`, `getInstances`
|
|
4868
|
+
- **Controller:** `claimController`, `releaseController`, `isController`, `getController`
|
|
4869
|
+
- **Messaging:** `broadcast`, `sendToController`, `sendToTab`
|
|
4870
|
+
- **Listeners:** `onMessage`, `onInstanceChange`
|
|
4871
|
+
- **Property:** `tabId` (available after `register()`)
|
|
4872
|
+
|
|
4873
|
+
### Browser Extension Messaging (`window.parent.postMessage`)
|
|
4874
|
+
- **`BG_FOCUS_TAB`** — Focus this tab and its browser window (requires `requestId` + `payload.appId`)
|
|
4875
|
+
- **`BG_SAVE_STATE`** / **`BG_LOAD_STATE`** / **`BG_CLEAR_STATE`** — State persistence (prefer `this.background`)
|
|
4876
|
+
- **`BG_SET_PENDING_OP`** / **`BG_GET_PENDING_OP`** / **`BG_CLEAR_PENDING_OP`** — Pending ops (prefer `this.background`)
|
|
4877
|
+
|
|
4421
4878
|
### Lifecycle
|
|
4422
4879
|
1. `constructor()` - Initialize
|
|
4423
4880
|
2. `connectedCallback()` - Added to DOM
|