@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.
- package/dist/WeaveAPIClient.d.ts +49 -0
- package/dist/WeaveAPIClient.d.ts.map +1 -1
- 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 +370 -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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/templates/WEAVE_SPEC.md +417 -1
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weave Base App
|
|
3
|
+
*
|
|
4
|
+
* Base class for all Weave apps. Provides common functionality and lifecycle methods.
|
|
5
|
+
* Third-party developers should extend this class instead of HTMLElement directly.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Define your settings and state types
|
|
10
|
+
* interface MyAppSettings {
|
|
11
|
+
* apiKey: string;
|
|
12
|
+
* endpoint: string;
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* interface MyAppState {
|
|
16
|
+
* count: number;
|
|
17
|
+
* isLoading: boolean;
|
|
18
|
+
* data: any[];
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* class MyApp extends WeaveBaseApp<MyAppSettings, MyAppState> {
|
|
22
|
+
* constructor() {
|
|
23
|
+
* super({
|
|
24
|
+
* id: 'my-app',
|
|
25
|
+
* name: 'My App',
|
|
26
|
+
* version: '1.0.0',
|
|
27
|
+
* category: 'utility',
|
|
28
|
+
* description: 'My custom app',
|
|
29
|
+
* author: 'Your Name',
|
|
30
|
+
* tags: ['custom'],
|
|
31
|
+
* // Enable auto-persist to survive page reloads
|
|
32
|
+
* persistState: true,
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* // Settings are now type-safe!
|
|
36
|
+
* const apiKey = this.appSettings?.apiKey; // ✅ TypeScript knows this exists
|
|
37
|
+
*
|
|
38
|
+
* // Initialize state with proper typing
|
|
39
|
+
* this.state = {
|
|
40
|
+
* count: 0,
|
|
41
|
+
* isLoading: false,
|
|
42
|
+
* data: []
|
|
43
|
+
* };
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* render() {
|
|
47
|
+
* return `<div>My App Content</div>`;
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* setupEventListeners() {
|
|
51
|
+
* // setState is also type-safe and auto-persists if enabled
|
|
52
|
+
* this.setState({ count: this.state.count + 1 }); // ✅ Type-safe
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* customElements.define('my-app', MyApp);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Simple debounce utility
|
|
61
|
+
*/
|
|
62
|
+
function debounce(fn, delay) {
|
|
63
|
+
let timeoutId = null;
|
|
64
|
+
return (...args) => {
|
|
65
|
+
if (timeoutId) {
|
|
66
|
+
clearTimeout(timeoutId);
|
|
67
|
+
}
|
|
68
|
+
timeoutId = setTimeout(() => {
|
|
69
|
+
fn(...args);
|
|
70
|
+
timeoutId = null;
|
|
71
|
+
}, delay);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Base class for Weave apps
|
|
76
|
+
* @template TSettings - Type for app settings, defaults to an empty object
|
|
77
|
+
* @template TState - Type for app state, defaults to an empty object
|
|
78
|
+
*/
|
|
79
|
+
export class WeaveBaseApp extends HTMLElement {
|
|
80
|
+
/**
|
|
81
|
+
* Creates a new Weave app
|
|
82
|
+
* @param appInfo - App metadata configuration
|
|
83
|
+
*/
|
|
84
|
+
constructor(appInfo) {
|
|
85
|
+
super();
|
|
86
|
+
/** App-specific state (override in subclass) */
|
|
87
|
+
this.state = {};
|
|
88
|
+
/** Background API for state persistence */
|
|
89
|
+
this._backgroundAPI = null;
|
|
90
|
+
/** Cron API for scheduled tasks */
|
|
91
|
+
this._cronAPI = null;
|
|
92
|
+
/** App Instance API for cross-tab communication */
|
|
93
|
+
this._instancesAPI = null;
|
|
94
|
+
/** Debounced persist function */
|
|
95
|
+
this._debouncedPersist = null;
|
|
96
|
+
/** Flag to track if state has been restored */
|
|
97
|
+
this._stateRestored = false;
|
|
98
|
+
/** App UUID from registry */
|
|
99
|
+
this._appUuid = null;
|
|
100
|
+
/** User's company role (e.g., 'admin', 'member') */
|
|
101
|
+
this.companyRole = null;
|
|
102
|
+
/** Current user object */
|
|
103
|
+
this.currentUser = null;
|
|
104
|
+
/** Current company object */
|
|
105
|
+
this.currentCompany = null;
|
|
106
|
+
// Validate required fields
|
|
107
|
+
if (!appInfo.id || !appInfo.name || !appInfo.version) {
|
|
108
|
+
throw new Error('WeaveBaseApp: id, name, and version are required');
|
|
109
|
+
}
|
|
110
|
+
this.appInfo = appInfo;
|
|
111
|
+
// Create shadow DOM for style isolation and store reference
|
|
112
|
+
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
|
113
|
+
// Read UUID and settings from global registry (set by BackgroundAppManager before element creation)
|
|
114
|
+
// This allows the constructor to access both UUID and settings immediately
|
|
115
|
+
const registry = window.__weaveAppRegistry;
|
|
116
|
+
const tagName = this.tagName.toLowerCase();
|
|
117
|
+
const registryData = registry?.[tagName];
|
|
118
|
+
// Set app settings if available in registry
|
|
119
|
+
if (registryData?.settings) {
|
|
120
|
+
this.appSettings = registryData.settings;
|
|
121
|
+
}
|
|
122
|
+
// Store app UUID for background API
|
|
123
|
+
this._appUuid = registryData?.uuid || null;
|
|
124
|
+
// Store user context for conditional rendering and personalization
|
|
125
|
+
this.currentUser = registryData?.currentUser || null;
|
|
126
|
+
this.currentCompany = registryData?.currentCompany || null;
|
|
127
|
+
this.companyRole = registryData?.companyRole || null;
|
|
128
|
+
// Set app UUID on the global API client so it's included in all requests
|
|
129
|
+
// This allows background services to make API calls without the app being open
|
|
130
|
+
if (window.weaveAPI) {
|
|
131
|
+
if (this._appUuid) {
|
|
132
|
+
// Create a new API client instance for THIS app
|
|
133
|
+
this.weaveAPI = new window.WeaveAPIClient();
|
|
134
|
+
this.weaveAPI.setAppId(this._appUuid);
|
|
135
|
+
// Create background API instance for state persistence
|
|
136
|
+
if (window.WeaveBackgroundAPI) {
|
|
137
|
+
this._backgroundAPI = new window.WeaveBackgroundAPI(this._appUuid);
|
|
138
|
+
}
|
|
139
|
+
// Create cron API instance for scheduled tasks
|
|
140
|
+
if (window.WeaveCronAPI) {
|
|
141
|
+
this._cronAPI = new window.WeaveCronAPI(this._appUuid);
|
|
142
|
+
this._cronAPI.initialize();
|
|
143
|
+
}
|
|
144
|
+
// Create app instance API for cross-tab communication
|
|
145
|
+
if (window.WeaveAppInstanceAPI) {
|
|
146
|
+
this._instancesAPI = new window.WeaveAppInstanceAPI(this._appUuid);
|
|
147
|
+
}
|
|
148
|
+
// Set up debounced persist if enabled
|
|
149
|
+
if (appInfo.persistState) {
|
|
150
|
+
const delay = appInfo.persistDebounce ?? 100;
|
|
151
|
+
this._debouncedPersist = debounce(() => {
|
|
152
|
+
this._persistState();
|
|
153
|
+
}, delay);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.warn('⚠️ App UUID not found in registry - API calls may fail. App:', appInfo.id);
|
|
158
|
+
// Fallback to global API client (will have wrong app ID, but better than nothing)
|
|
159
|
+
this.weaveAPI = window.weaveAPI;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.warn('⚠️ window.weaveAPI not available - API calls will fail. App:', appInfo.id);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get the background API for state persistence and pending operations
|
|
168
|
+
* Use this for manual state management or multi-page flows
|
|
169
|
+
*/
|
|
170
|
+
get background() {
|
|
171
|
+
return this._backgroundAPI;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get the cron API for scheduled tasks
|
|
175
|
+
*/
|
|
176
|
+
get cron() {
|
|
177
|
+
return this._cronAPI;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get the app instance API for cross-tab communication
|
|
181
|
+
*
|
|
182
|
+
* Use this to:
|
|
183
|
+
* - Discover other instances of your app across browser tabs
|
|
184
|
+
* - Claim/release controller status (for automation coordination)
|
|
185
|
+
* - Send messages between instances
|
|
186
|
+
* - Listen for instance changes
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* // Register this instance and check for others
|
|
190
|
+
* const instances = await this.instances.register();
|
|
191
|
+
* if (instances.length > 1) {
|
|
192
|
+
* // Other instances exist
|
|
193
|
+
* }
|
|
194
|
+
*
|
|
195
|
+
* // Claim controller status
|
|
196
|
+
* const claimed = await this.instances.claimController();
|
|
197
|
+
*
|
|
198
|
+
* // Listen for messages from other instances
|
|
199
|
+
* this.instances.onMessage((msg) => {
|
|
200
|
+
* if (msg.type === 'YIELD_CONTROL') {
|
|
201
|
+
* // Another tab wants control
|
|
202
|
+
* }
|
|
203
|
+
* });
|
|
204
|
+
*/
|
|
205
|
+
get instances() {
|
|
206
|
+
return this._instancesAPI;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Register a cron job with a callback
|
|
210
|
+
*
|
|
211
|
+
* @param cronExpression - Standard cron expression (5 or 6 fields)
|
|
212
|
+
* - 5 fields: minute hour dayOfMonth month dayOfWeek
|
|
213
|
+
* - 6 fields: second minute hour dayOfMonth month dayOfWeek
|
|
214
|
+
* @param callback - Function to call when cron triggers
|
|
215
|
+
* @param jobName - Optional unique name for the job
|
|
216
|
+
* @returns Job ID if successful, null otherwise
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* // Every second
|
|
220
|
+
* this.cronTab('* * * * * *', this.handleEverySecond);
|
|
221
|
+
*
|
|
222
|
+
* // Every minute
|
|
223
|
+
* this.cronTab('* * * * *', this.handleEveryMinute);
|
|
224
|
+
*
|
|
225
|
+
* // Every 5 seconds
|
|
226
|
+
* this.cronTab('0/5 * * * * *', this.handleEvery5Seconds);
|
|
227
|
+
*
|
|
228
|
+
* // Every hour at minute 0
|
|
229
|
+
* this.cronTab('0 * * * *', this.handleEveryHour);
|
|
230
|
+
*/
|
|
231
|
+
async cronTab(cronExpression, callback, jobName) {
|
|
232
|
+
if (!this._cronAPI) {
|
|
233
|
+
console.error('Cron API not available - app UUID may be missing');
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
// Bind callback to this instance
|
|
237
|
+
const boundCallback = callback.bind(this);
|
|
238
|
+
return this._cronAPI.register(cronExpression, boundCallback, jobName);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Unregister a cron job by name
|
|
242
|
+
*/
|
|
243
|
+
async cronUnregister(jobName) {
|
|
244
|
+
if (!this._cronAPI)
|
|
245
|
+
return false;
|
|
246
|
+
return this._cronAPI.unregister(jobName);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Unregister all cron jobs for this app
|
|
250
|
+
*/
|
|
251
|
+
async cronUnregisterAll() {
|
|
252
|
+
if (!this._cronAPI)
|
|
253
|
+
return 0;
|
|
254
|
+
return this._cronAPI.unregisterAll();
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get shadow root (override native property)
|
|
258
|
+
*/
|
|
259
|
+
get shadowRoot() {
|
|
260
|
+
return this._shadowRoot;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Render method - must be implemented by subclass
|
|
264
|
+
* Should return HTML string or directly manipulate shadowRoot
|
|
265
|
+
*/
|
|
266
|
+
render() {
|
|
267
|
+
// Default implementation - subclasses should override
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Setup event listeners - optional override in subclass
|
|
271
|
+
*/
|
|
272
|
+
setupEventListeners() {
|
|
273
|
+
// Default implementation - subclasses can override
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Initialize background service
|
|
277
|
+
* Called by BackgroundAppManager after instantiation
|
|
278
|
+
*/
|
|
279
|
+
initializeBackgroundService() {
|
|
280
|
+
if (this.onBackgroundService) {
|
|
281
|
+
this.onBackgroundService();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Handle URL change
|
|
286
|
+
* Called by BackgroundAppManager when URL changes
|
|
287
|
+
*/
|
|
288
|
+
handleUrlChange(url) {
|
|
289
|
+
if (this.onUrlChange) {
|
|
290
|
+
this.onUrlChange(url);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Handle server event
|
|
295
|
+
* Called by BackgroundAppManager when a WebSocket event is received for this app
|
|
296
|
+
*/
|
|
297
|
+
handleServerEvent(event, data) {
|
|
298
|
+
if (this.onServerEvent) {
|
|
299
|
+
this.onServerEvent(event, data);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Lifecycle: Called when element is added to DOM
|
|
304
|
+
* This happens when user opens the app in the drawer
|
|
305
|
+
*/
|
|
306
|
+
connectedCallback() {
|
|
307
|
+
// Render the app UI
|
|
308
|
+
this.render();
|
|
309
|
+
// Setup event listeners for UI
|
|
310
|
+
this.setupEventListeners();
|
|
311
|
+
// Notify Weave Platform that app UI is ready
|
|
312
|
+
this.dispatchEvent(new CustomEvent('weave-app-ready', {
|
|
313
|
+
detail: {
|
|
314
|
+
appInfo: this.appInfo,
|
|
315
|
+
timestamp: new Date().toISOString()
|
|
316
|
+
},
|
|
317
|
+
bubbles: true,
|
|
318
|
+
composed: true
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Lifecycle: Called when element is removed from DOM
|
|
323
|
+
*/
|
|
324
|
+
disconnectedCallback() {
|
|
325
|
+
this.cleanup();
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Cleanup method - optional override in subclass
|
|
329
|
+
* Called when app is disconnected from DOM
|
|
330
|
+
*/
|
|
331
|
+
cleanup() {
|
|
332
|
+
// Default implementation - subclasses can override
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get app configuration (for Weave Platform)
|
|
336
|
+
*/
|
|
337
|
+
getAppInfo() {
|
|
338
|
+
return this.appInfo;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Get current app state (for Weave Platform)
|
|
342
|
+
*/
|
|
343
|
+
getAppState() {
|
|
344
|
+
return {
|
|
345
|
+
...this.state,
|
|
346
|
+
lastUpdated: new Date().toISOString()
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Set app configuration (for Weave Platform)
|
|
351
|
+
* Override this method to handle configuration updates
|
|
352
|
+
*/
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
354
|
+
setAppConfig(_config) {
|
|
355
|
+
// Default implementation - subclasses can override
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Helper: Update app state
|
|
359
|
+
* If persistState is enabled, state is automatically saved to background service
|
|
360
|
+
*/
|
|
361
|
+
setState(updates) {
|
|
362
|
+
this.state = { ...this.state, ...updates };
|
|
363
|
+
// Auto-persist if enabled
|
|
364
|
+
if (this.appInfo.persistState && this._debouncedPersist) {
|
|
365
|
+
this._debouncedPersist();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Persist current state to background service
|
|
370
|
+
* Called automatically when persistState is enabled
|
|
371
|
+
*/
|
|
372
|
+
async _persistState() {
|
|
373
|
+
if (!this._backgroundAPI)
|
|
374
|
+
return;
|
|
375
|
+
try {
|
|
376
|
+
await this._backgroundAPI.saveState(this.state);
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
console.error('❌ Failed to persist state:', error);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Restore state from background service
|
|
384
|
+
* Called by BackgroundAppManager during initialization
|
|
385
|
+
*/
|
|
386
|
+
async restoreState() {
|
|
387
|
+
if (!this._backgroundAPI || !this.appInfo.persistState) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
if (this._stateRestored) {
|
|
391
|
+
return true; // Already restored
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
const savedState = await this._backgroundAPI.loadState();
|
|
395
|
+
if (savedState) {
|
|
396
|
+
this.state = { ...this.state, ...savedState };
|
|
397
|
+
this._stateRestored = true;
|
|
398
|
+
console.log('✅ State restored for app:', this.appInfo.id);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error('❌ Failed to restore state:', error);
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Helper: Render HTML into shadow root
|
|
409
|
+
*/
|
|
410
|
+
renderHTML(html) {
|
|
411
|
+
if (this._shadowRoot) {
|
|
412
|
+
this._shadowRoot.innerHTML = html;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
console.error('❌ No shadow root available!');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Helper: Query element in shadow root
|
|
420
|
+
*/
|
|
421
|
+
query(selector) {
|
|
422
|
+
return this._shadowRoot?.querySelector(selector) || null;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Helper: Query all elements in shadow root
|
|
426
|
+
*/
|
|
427
|
+
queryAll(selector) {
|
|
428
|
+
return this._shadowRoot?.querySelectorAll(selector) || [];
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Export for global usage
|
|
432
|
+
if (typeof window !== 'undefined') {
|
|
433
|
+
window.WeaveBaseApp = WeaveBaseApp;
|
|
434
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeaveCronAPI - Client API for apps to register cron jobs
|
|
3
|
+
*
|
|
4
|
+
* Apps can register callbacks to be executed on a schedule using standard cron syntax.
|
|
5
|
+
* The browser extension's background script acts as the master clock.
|
|
6
|
+
*
|
|
7
|
+
* Cron syntax (5 or 6 fields):
|
|
8
|
+
* - 5 fields: minute hour dayOfMonth month dayOfWeek
|
|
9
|
+
* - 6 fields: second minute hour dayOfMonth month dayOfWeek
|
|
10
|
+
*
|
|
11
|
+
* Special characters: asterisk (any), comma (list), dash (range), slash (step)
|
|
12
|
+
*/
|
|
13
|
+
interface CronJob {
|
|
14
|
+
id: string;
|
|
15
|
+
appId: string;
|
|
16
|
+
tabId: number;
|
|
17
|
+
cronExpression: string;
|
|
18
|
+
jobName: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
}
|
|
21
|
+
type CronCallback = () => void | Promise<void>;
|
|
22
|
+
export declare class WeaveCronAPI {
|
|
23
|
+
private appId;
|
|
24
|
+
private callbacks;
|
|
25
|
+
private jobIds;
|
|
26
|
+
private pendingRequests;
|
|
27
|
+
private messageListener;
|
|
28
|
+
private isInitialized;
|
|
29
|
+
constructor(appId: string);
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the cron API (sets up message listener)
|
|
32
|
+
*/
|
|
33
|
+
initialize(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Cleanup - unregister all jobs and remove listener
|
|
36
|
+
*/
|
|
37
|
+
destroy(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Register a cron job
|
|
40
|
+
* @param cronExpression - Cron expression (5 or 6 fields)
|
|
41
|
+
* @param callback - Function to call when cron triggers
|
|
42
|
+
* @param jobName - Optional unique name for the job (defaults to callback name or random)
|
|
43
|
+
* @returns Job ID if successful
|
|
44
|
+
*/
|
|
45
|
+
register(cronExpression: string, callback: CronCallback, jobName?: string): Promise<string | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Unregister a cron job by name
|
|
48
|
+
*/
|
|
49
|
+
unregister(jobName: string): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Unregister all cron jobs for this app
|
|
52
|
+
*/
|
|
53
|
+
unregisterAll(): Promise<number>;
|
|
54
|
+
/**
|
|
55
|
+
* List all cron jobs for this app
|
|
56
|
+
*/
|
|
57
|
+
list(): Promise<CronJob[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Handle incoming messages
|
|
60
|
+
*/
|
|
61
|
+
private handleMessage;
|
|
62
|
+
/**
|
|
63
|
+
* Send request to content script bridge
|
|
64
|
+
*/
|
|
65
|
+
private sendRequest;
|
|
66
|
+
}
|
|
67
|
+
export default WeaveCronAPI;
|
|
68
|
+
//# sourceMappingURL=WeaveCronAPI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WeaveCronAPI.d.ts","sourceRoot":"","sources":["../../../src/WeaveCronAPI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,UAAU,OAAO;IACf,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAcD,KAAK,YAAY,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/C,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,eAAe,CAA2F;IAClH,OAAO,CAAC,eAAe,CAAgD;IACvE,OAAO,CAAC,aAAa,CAAS;gBAElB,KAAK,EAAE,MAAM;IAIzB;;OAEG;IACH,UAAU,IAAI,IAAI;IAUlB;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAe9B;;;;;;OAMG;IACG,QAAQ,CACZ,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAwBzB;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkBnD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IActC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAQhC;;OAEG;IACH,OAAO,CAAC,aAAa;IAmCrB;;OAEG;IACH,OAAO,CAAC,WAAW;CAqBpB;AAED,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeaveCronAPI - Client API for apps to register cron jobs
|
|
3
|
+
*
|
|
4
|
+
* Apps can register callbacks to be executed on a schedule using standard cron syntax.
|
|
5
|
+
* The browser extension's background script acts as the master clock.
|
|
6
|
+
*
|
|
7
|
+
* Cron syntax (5 or 6 fields):
|
|
8
|
+
* - 5 fields: minute hour dayOfMonth month dayOfWeek
|
|
9
|
+
* - 6 fields: second minute hour dayOfMonth month dayOfWeek
|
|
10
|
+
*
|
|
11
|
+
* Special characters: asterisk (any), comma (list), dash (range), slash (step)
|
|
12
|
+
*/
|
|
13
|
+
export class WeaveCronAPI {
|
|
14
|
+
constructor(appId) {
|
|
15
|
+
this.callbacks = new Map();
|
|
16
|
+
this.jobIds = new Map(); // jobName -> jobId
|
|
17
|
+
this.pendingRequests = new Map();
|
|
18
|
+
this.messageListener = null;
|
|
19
|
+
this.isInitialized = false;
|
|
20
|
+
this.appId = appId;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the cron API (sets up message listener)
|
|
24
|
+
*/
|
|
25
|
+
initialize() {
|
|
26
|
+
if (this.isInitialized)
|
|
27
|
+
return;
|
|
28
|
+
this.messageListener = (event) => {
|
|
29
|
+
this.handleMessage(event);
|
|
30
|
+
};
|
|
31
|
+
window.addEventListener('message', this.messageListener);
|
|
32
|
+
this.isInitialized = true;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Cleanup - unregister all jobs and remove listener
|
|
36
|
+
*/
|
|
37
|
+
async destroy() {
|
|
38
|
+
// Unregister all jobs for this app
|
|
39
|
+
await this.unregisterAll();
|
|
40
|
+
// Remove message listener
|
|
41
|
+
if (this.messageListener) {
|
|
42
|
+
window.removeEventListener('message', this.messageListener);
|
|
43
|
+
this.messageListener = null;
|
|
44
|
+
}
|
|
45
|
+
this.callbacks.clear();
|
|
46
|
+
this.jobIds.clear();
|
|
47
|
+
this.isInitialized = false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Register a cron job
|
|
51
|
+
* @param cronExpression - Cron expression (5 or 6 fields)
|
|
52
|
+
* @param callback - Function to call when cron triggers
|
|
53
|
+
* @param jobName - Optional unique name for the job (defaults to callback name or random)
|
|
54
|
+
* @returns Job ID if successful
|
|
55
|
+
*/
|
|
56
|
+
async register(cronExpression, callback, jobName) {
|
|
57
|
+
const name = jobName || callback.name || `job_${Date.now()}`;
|
|
58
|
+
// Store callback locally
|
|
59
|
+
this.callbacks.set(name, callback);
|
|
60
|
+
// Send registration request to background
|
|
61
|
+
const response = await this.sendRequest('CRON_REGISTER', {
|
|
62
|
+
appId: this.appId,
|
|
63
|
+
cronExpression,
|
|
64
|
+
jobName: name,
|
|
65
|
+
});
|
|
66
|
+
if (response.success && response.jobId) {
|
|
67
|
+
this.jobIds.set(name, response.jobId);
|
|
68
|
+
return response.jobId;
|
|
69
|
+
}
|
|
70
|
+
// Registration failed, remove callback
|
|
71
|
+
this.callbacks.delete(name);
|
|
72
|
+
console.error(`Failed to register cron job: ${response.error}`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Unregister a cron job by name
|
|
77
|
+
*/
|
|
78
|
+
async unregister(jobName) {
|
|
79
|
+
const jobId = this.jobIds.get(jobName);
|
|
80
|
+
if (!jobId) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const response = await this.sendRequest('CRON_UNREGISTER', {
|
|
84
|
+
jobId,
|
|
85
|
+
});
|
|
86
|
+
if (response.success) {
|
|
87
|
+
this.callbacks.delete(jobName);
|
|
88
|
+
this.jobIds.delete(jobName);
|
|
89
|
+
}
|
|
90
|
+
return response.success;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Unregister all cron jobs for this app
|
|
94
|
+
*/
|
|
95
|
+
async unregisterAll() {
|
|
96
|
+
const response = await this.sendRequest('CRON_UNREGISTER_APP', {
|
|
97
|
+
appId: this.appId,
|
|
98
|
+
});
|
|
99
|
+
if (response.success) {
|
|
100
|
+
this.callbacks.clear();
|
|
101
|
+
this.jobIds.clear();
|
|
102
|
+
return response.count;
|
|
103
|
+
}
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* List all cron jobs for this app
|
|
108
|
+
*/
|
|
109
|
+
async list() {
|
|
110
|
+
const response = await this.sendRequest('CRON_LIST', {
|
|
111
|
+
appId: this.appId,
|
|
112
|
+
});
|
|
113
|
+
return response.jobs || [];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Handle incoming messages
|
|
117
|
+
*/
|
|
118
|
+
handleMessage(event) {
|
|
119
|
+
const { data } = event;
|
|
120
|
+
// Handle cron tick
|
|
121
|
+
if (data?.type === 'CRON_TICK') {
|
|
122
|
+
const { appId, jobName } = data.payload;
|
|
123
|
+
// Only handle ticks for this app
|
|
124
|
+
if (appId !== this.appId)
|
|
125
|
+
return;
|
|
126
|
+
const callback = this.callbacks.get(jobName);
|
|
127
|
+
if (callback) {
|
|
128
|
+
try {
|
|
129
|
+
const result = callback();
|
|
130
|
+
// Handle async callbacks
|
|
131
|
+
if (result instanceof Promise) {
|
|
132
|
+
result.catch(err => console.error(`Cron job ${jobName} error:`, err));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error(`Cron job ${jobName} error:`, error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Handle responses
|
|
142
|
+
if (data?.type?.endsWith('_RESPONSE') && data.requestId) {
|
|
143
|
+
const pending = this.pendingRequests.get(data.requestId);
|
|
144
|
+
if (pending) {
|
|
145
|
+
this.pendingRequests.delete(data.requestId);
|
|
146
|
+
pending.resolve(data.payload);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Send request to content script bridge
|
|
152
|
+
*/
|
|
153
|
+
sendRequest(type, payload) {
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
const requestId = `cron_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
156
|
+
this.pendingRequests.set(requestId, { resolve, reject });
|
|
157
|
+
// Timeout after 5 seconds
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
if (this.pendingRequests.has(requestId)) {
|
|
160
|
+
this.pendingRequests.delete(requestId);
|
|
161
|
+
reject(new Error('Cron request timeout'));
|
|
162
|
+
}
|
|
163
|
+
}, 5000);
|
|
164
|
+
window.parent.postMessage({
|
|
165
|
+
type,
|
|
166
|
+
requestId,
|
|
167
|
+
payload,
|
|
168
|
+
}, '*');
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export default WeaveCronAPI;
|