@weave-apps/sdk 0.1.11 → 0.1.13

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 CHANGED
@@ -12,6 +12,7 @@ Official SDK for building third-party applications for the Weave Platform.
12
12
  - ✅ **Shadow DOM Isolation** - Scoped styles and encapsulation
13
13
  - ✅ **Background Services** - Run code without user interaction
14
14
  - ✅ **State Persistence** - Survive page reloads with auto-persist
15
+ - ✅ **Scheduled Tasks** - Cron jobs with standard cron syntax
15
16
  - ✅ **Multi-Page Flows** - Pending operations across navigations
16
17
  - ✅ **Form Integration** - Auto-fill forms with extracted data
17
18
  - ✅ **AI Integration** - Leverage AI for smart form filling
@@ -58,6 +59,9 @@ Official SDK for building third-party applications for the Weave Platform.
58
59
  - [Auto-Persist (Recommended)](#auto-persist-recommended)
59
60
  - [Multi-Page Flows (Pending Operations)](#multi-page-flows-pending-operations)
60
61
  - [State TTL](#state-ttl)
62
+ - [Scheduled Tasks (Cron Jobs)](#scheduled-tasks-cron-jobs)
63
+ - [🔮 Secret Hack: Persistent Cron](#-secret-hack-persistent-cron)
64
+ - [Cron Syntax](#cron-syntax)
61
65
  - [Settings & Configuration](#settings--configuration)
62
66
  - [Error Handling](#error-handling)
63
67
  - [Performance Tips](#performance-tips)
@@ -540,6 +544,92 @@ class DataTransferApp extends WeaveBaseApp {
540
544
 
541
545
  Persisted state expires after **5 minutes** by default. This prevents stale state from accumulating.
542
546
 
547
+ ### Scheduled Tasks (Cron Jobs)
548
+
549
+ Apps can register scheduled tasks using standard cron syntax. The browser extension acts as the master clock.
550
+
551
+ ```typescript
552
+ class PollingApp extends WeaveBaseApp {
553
+ constructor() {
554
+ super({
555
+ id: 'polling-app',
556
+ name: 'Polling App',
557
+ version: '1.0.0',
558
+ category: 'utility',
559
+ description: 'App with scheduled polling',
560
+ author: 'Your Name',
561
+ persistState: true, // Important for cron!
562
+ });
563
+
564
+ this.state = {
565
+ cronEnabled: false, // Track cron state
566
+ tickCount: 0
567
+ };
568
+ }
569
+
570
+ async onBackgroundService() {
571
+ // Re-register cron if it was enabled before page reload
572
+ if (this.state.cronEnabled) {
573
+ await this.startPolling();
574
+ }
575
+ }
576
+
577
+ async startPolling() {
578
+ // Register cron job - every 5 seconds
579
+ const jobId = await this.cronTab('*/5 * * * * *', this.handleTick, 'poller');
580
+ if (jobId) {
581
+ this.setState({ cronEnabled: true });
582
+ }
583
+ }
584
+
585
+ async stopPolling() {
586
+ await this.cronUnregisterAll();
587
+ this.setState({ cronEnabled: false });
588
+ }
589
+
590
+ handleTick() {
591
+ this.setState({ tickCount: this.state.tickCount + 1 });
592
+ console.log('Tick!', this.state.tickCount);
593
+
594
+ // Update UI if app is open
595
+ if (this.isConnected) {
596
+ this.render();
597
+ this.setupEventListeners();
598
+ }
599
+ }
600
+ }
601
+ ```
602
+
603
+ #### 🔮 Secret Hack: Persistent Cron
604
+
605
+ Cron jobs don't survive page reloads by default. The **secret hack** is to combine `persistState: true` with a `cronEnabled` state flag:
606
+
607
+ 1. **Track cron state** - Add `cronEnabled: boolean` to your state
608
+ 2. **Set flag when starting** - `this.setState({ cronEnabled: true })`
609
+ 3. **Re-register on reload** - Check `this.state.cronEnabled` in `onBackgroundService()`
610
+
611
+ This way, when the page reloads, your state is restored (including `cronEnabled: true`), and `onBackgroundService()` automatically re-registers the cron job!
612
+
613
+ #### Cron Syntax
614
+
615
+ ```
616
+ ┌───────────── second (0-59) - optional
617
+ │ ┌───────────── minute (0-59)
618
+ │ │ ┌───────────── hour (0-23)
619
+ │ │ │ ┌───────────── day of month (1-31)
620
+ │ │ │ │ ┌───────────── month (1-12)
621
+ │ │ │ │ │ ┌───────────── day of week (0-6)
622
+ │ │ │ │ │ │
623
+ * * * * * *
624
+ ```
625
+
626
+ **Common patterns:**
627
+ - `* * * * * *` - Every second
628
+ - `*/5 * * * * *` - Every 5 seconds
629
+ - `*/30 * * * * *` - Every 30 seconds
630
+ - `0 * * * * *` - Every minute
631
+ - `0 */5 * * * *` - Every 5 minutes
632
+
543
633
  ### Settings & Configuration
544
634
 
545
635
  Settings are injected from the Enterprise Console and displayed as auto-extracted form fields:
@@ -56,8 +56,14 @@
56
56
  * customElements.define('my-app', MyApp);
57
57
  * ```
58
58
  */
59
- import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
60
- export type { PendingOperation } from './WeaveBackgroundAPI';
59
+ /**
60
+ * Pending operation for multi-page flows
61
+ */
62
+ export interface PendingOperation {
63
+ type: string;
64
+ data: any;
65
+ targetUrl?: string;
66
+ }
61
67
  /**
62
68
  * App metadata configuration
63
69
  */
@@ -108,6 +114,8 @@ export declare abstract class WeaveBaseApp<TSettings = Record<string, any>, TSta
108
114
  protected weaveAPI: any;
109
115
  /** Background API for state persistence */
110
116
  private _backgroundAPI;
117
+ /** Cron API for scheduled tasks */
118
+ private _cronAPI;
111
119
  /** Debounced persist function */
112
120
  private _debouncedPersist;
113
121
  /** Flag to track if state has been restored */
@@ -123,7 +131,43 @@ export declare abstract class WeaveBaseApp<TSettings = Record<string, any>, TSta
123
131
  * Get the background API for state persistence and pending operations
124
132
  * Use this for manual state management or multi-page flows
125
133
  */
126
- protected get background(): WeaveBackgroundAPI | null;
134
+ protected get background(): any;
135
+ /**
136
+ * Get the cron API for scheduled tasks
137
+ */
138
+ protected get cron(): any;
139
+ /**
140
+ * Register a cron job with a callback
141
+ *
142
+ * @param cronExpression - Standard cron expression (5 or 6 fields)
143
+ * - 5 fields: minute hour dayOfMonth month dayOfWeek
144
+ * - 6 fields: second minute hour dayOfMonth month dayOfWeek
145
+ * @param callback - Function to call when cron triggers
146
+ * @param jobName - Optional unique name for the job
147
+ * @returns Job ID if successful, null otherwise
148
+ *
149
+ * @example
150
+ * // Every second
151
+ * this.cronTab('* * * * * *', this.handleEverySecond);
152
+ *
153
+ * // Every minute
154
+ * this.cronTab('* * * * *', this.handleEveryMinute);
155
+ *
156
+ * // Every 5 seconds
157
+ * this.cronTab('0/5 * * * * *', this.handleEvery5Seconds);
158
+ *
159
+ * // Every hour at minute 0
160
+ * this.cronTab('0 * * * *', this.handleEveryHour);
161
+ */
162
+ protected cronTab(cronExpression: string, callback: () => void | Promise<void>, jobName?: string): Promise<string | null>;
163
+ /**
164
+ * Unregister a cron job by name
165
+ */
166
+ protected cronUnregister(jobName: string): Promise<boolean>;
167
+ /**
168
+ * Unregister all cron jobs for this app
169
+ */
170
+ protected cronUnregisterAll(): Promise<number>;
127
171
  /**
128
172
  * Get shadow root (override native property)
129
173
  */
@@ -1 +1 @@
1
- {"version":3,"file":"WeaveBaseApp.d.ts","sourceRoot":"","sources":["../src/WeaveBaseApp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WeaveBaseApp.d.ts","sourceRoot":"","sources":["../src/WeaveBaseApp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAKH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAMhB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAsBD;;;;GAIG;AACH,8BAAsB,YAAY,CAChC,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAC5B,SAAQ,WAAW;IACnB,mBAAmB;IACnB,SAAS,CAAC,OAAO,EAAE,YAAY,CAAC;IAEhC,2CAA2C;IAC3C,SAAS,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IAElC,gDAAgD;IAChD,SAAS,CAAC,KAAK,EAAE,MAAM,CAAgB;IAEvC,sCAAsC;IACtC,OAAO,CAAC,WAAW,CAAa;IAEhC,mEAAmE;IACnE,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC;IAExB,2CAA2C;IAC3C,OAAO,CAAC,cAAc,CAAa;IAEnC,mCAAmC;IACnC,OAAO,CAAC,QAAQ,CAAa;IAE7B,iCAAiC;IACjC,OAAO,CAAC,iBAAiB,CAA6B;IAEtD,+CAA+C;IAC/C,OAAO,CAAC,cAAc,CAAkB;IAExC,6BAA6B;IAC7B,OAAO,CAAC,QAAQ,CAAuB;IAEvC;;;OAGG;gBACS,OAAO,EAAE,YAAY;IA6DjC;;;OAGG;IACH,SAAS,KAAK,UAAU,IAAI,GAAG,CAE9B;IAED;;OAEG;IACH,SAAS,KAAK,IAAI,IAAI,GAAG,CAExB;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;cACa,OAAO,CACrB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACpC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAYzB;;OAEG;cACa,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKjE;;OAEG;cACa,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKpD;;OAEG;IACH,IAAI,UAAU,IAAI,UAAU,CAE3B;IAED;;;;OAIG;IACH,SAAS,CAAC,mBAAmB,CAAC,IAAI,IAAI;IAEtC;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAEzC;;;OAGG;IACH,SAAS,CAAC,MAAM,IAAI,IAAI;IAIxB;;OAEG;IACH,SAAS,CAAC,mBAAmB,IAAI,IAAI;IAIrC;;;OAGG;IACI,2BAA2B,IAAI,IAAI;IAO1C;;;OAGG;IACI,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMzC;;;OAGG;IACH,iBAAiB,IAAI,IAAI;IAoBzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAI5B;;;OAGG;IACH,SAAS,CAAC,OAAO,IAAI,IAAI;IAIzB;;OAEG;IACI,UAAU,IAAI,YAAY;IAIjC;;OAEG;IACI,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAOzC;;;OAGG;IAEI,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAIvD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI;IASlD;;;OAGG;YACW,aAAa;IAU3B;;;OAGG;IACU,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC;IAyB7C;;OAEG;IACH,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQxC;;OAEG;IACH,SAAS,CAAC,KAAK,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAIxE;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC;CAGjF"}
@@ -56,7 +56,6 @@
56
56
  * customElements.define('my-app', MyApp);
57
57
  * ```
58
58
  */
59
- import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
60
59
  /**
61
60
  * Simple debounce utility
62
61
  */
@@ -88,6 +87,8 @@ export class WeaveBaseApp extends HTMLElement {
88
87
  this.state = {};
89
88
  /** Background API for state persistence */
90
89
  this._backgroundAPI = null;
90
+ /** Cron API for scheduled tasks */
91
+ this._cronAPI = null;
91
92
  /** Debounced persist function */
92
93
  this._debouncedPersist = null;
93
94
  /** Flag to track if state has been restored */
@@ -120,7 +121,14 @@ export class WeaveBaseApp extends HTMLElement {
120
121
  this.weaveAPI = new window.WeaveAPIClient();
121
122
  this.weaveAPI.setAppId(this._appUuid);
122
123
  // Create background API instance for state persistence
123
- this._backgroundAPI = new WeaveBackgroundAPI(this._appUuid);
124
+ if (window.WeaveBackgroundAPI) {
125
+ this._backgroundAPI = new window.WeaveBackgroundAPI(this._appUuid);
126
+ }
127
+ // Create cron API instance for scheduled tasks
128
+ if (window.WeaveCronAPI) {
129
+ this._cronAPI = new window.WeaveCronAPI(this._appUuid);
130
+ this._cronAPI.initialize();
131
+ }
124
132
  // Set up debounced persist if enabled
125
133
  if (appInfo.persistState) {
126
134
  const delay = appInfo.persistDebounce ?? 100;
@@ -143,6 +151,60 @@ export class WeaveBaseApp extends HTMLElement {
143
151
  get background() {
144
152
  return this._backgroundAPI;
145
153
  }
154
+ /**
155
+ * Get the cron API for scheduled tasks
156
+ */
157
+ get cron() {
158
+ return this._cronAPI;
159
+ }
160
+ /**
161
+ * Register a cron job with a callback
162
+ *
163
+ * @param cronExpression - Standard cron expression (5 or 6 fields)
164
+ * - 5 fields: minute hour dayOfMonth month dayOfWeek
165
+ * - 6 fields: second minute hour dayOfMonth month dayOfWeek
166
+ * @param callback - Function to call when cron triggers
167
+ * @param jobName - Optional unique name for the job
168
+ * @returns Job ID if successful, null otherwise
169
+ *
170
+ * @example
171
+ * // Every second
172
+ * this.cronTab('* * * * * *', this.handleEverySecond);
173
+ *
174
+ * // Every minute
175
+ * this.cronTab('* * * * *', this.handleEveryMinute);
176
+ *
177
+ * // Every 5 seconds
178
+ * this.cronTab('0/5 * * * * *', this.handleEvery5Seconds);
179
+ *
180
+ * // Every hour at minute 0
181
+ * this.cronTab('0 * * * *', this.handleEveryHour);
182
+ */
183
+ async cronTab(cronExpression, callback, jobName) {
184
+ if (!this._cronAPI) {
185
+ console.error('Cron API not available - app UUID may be missing');
186
+ return null;
187
+ }
188
+ // Bind callback to this instance
189
+ const boundCallback = callback.bind(this);
190
+ return this._cronAPI.register(cronExpression, boundCallback, jobName);
191
+ }
192
+ /**
193
+ * Unregister a cron job by name
194
+ */
195
+ async cronUnregister(jobName) {
196
+ if (!this._cronAPI)
197
+ return false;
198
+ return this._cronAPI.unregister(jobName);
199
+ }
200
+ /**
201
+ * Unregister all cron jobs for this app
202
+ */
203
+ async cronUnregisterAll() {
204
+ if (!this._cronAPI)
205
+ return 0;
206
+ return this._cronAPI.unregisterAll();
207
+ }
146
208
  /**
147
209
  * Get shadow root (override native property)
148
210
  */
@@ -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;
package/dist/global.d.ts CHANGED
@@ -8,6 +8,7 @@ import { WeaveBaseApp } from './WeaveBaseApp';
8
8
  import { WeaveDOMAPI } from './WeaveDOMAPI';
9
9
  import { WeaveAPIClient } from './WeaveAPIClient';
10
10
  import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
11
+ import { WeaveCronAPI } from './WeaveCronAPI';
11
12
  declare global {
12
13
  interface Window {
13
14
  WeaveBaseApp: typeof WeaveBaseApp;
@@ -16,6 +17,7 @@ declare global {
16
17
  WeaveAPIClient: typeof WeaveAPIClient;
17
18
  weaveAPI: WeaveAPIClient;
18
19
  WeaveBackgroundAPI: typeof WeaveBackgroundAPI;
20
+ WeaveCronAPI: typeof WeaveCronAPI;
19
21
  }
20
22
  }
21
23
  //# sourceMappingURL=global.d.ts.map
@@ -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;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"}
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;AAG9C,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;KAAG;CACxC"}
package/dist/global.js CHANGED
@@ -10,6 +10,7 @@ import weavekDOM from './WeaveDOMAPI';
10
10
  import { WeaveAPIClient } from './WeaveAPIClient';
11
11
  import weaveAPI from './WeaveAPIClient';
12
12
  import { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
13
+ import { WeaveCronAPI } from './WeaveCronAPI';
13
14
  // Make SDK available globally
14
15
  window.WeaveBaseApp = WeaveBaseApp;
15
16
  window.WeaveDOMAPI = WeaveDOMAPI;
@@ -17,3 +18,4 @@ window.weaveDOM = weavekDOM;
17
18
  window.WeaveAPIClient = WeaveAPIClient;
18
19
  window.weaveAPI = weaveAPI;
19
20
  window.WeaveBackgroundAPI = WeaveBackgroundAPI;
21
+ window.WeaveCronAPI = WeaveCronAPI;
package/dist/index.d.ts CHANGED
@@ -9,5 +9,6 @@ export { default as weaveDOM } from './WeaveDOMAPI';
9
9
  export { WeaveAPIClient, type AIChatRequest, type AIChatResponse, type AppData, type CreateAppDataRequest, type UpdateAppDataRequest } from './WeaveAPIClient';
10
10
  export { default as weaveAPI } from './WeaveAPIClient';
11
11
  export { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
12
+ export { WeaveCronAPI } from './WeaveCronAPI';
12
13
  import './global';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -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;AAG1D,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,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"}
package/dist/index.js CHANGED
@@ -9,5 +9,6 @@ export { default as weaveDOM } from './WeaveDOMAPI';
9
9
  export { WeaveAPIClient } from './WeaveAPIClient';
10
10
  export { default as weaveAPI } from './WeaveAPIClient';
11
11
  export { WeaveBackgroundAPI } from './WeaveBackgroundAPI';
12
+ export { WeaveCronAPI } from './WeaveCronAPI';
12
13
  // Import global.ts to ensure Window interface augmentation is included
13
14
  import './global';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weave-apps/sdk",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "SDK for building Weave Micro Apps",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,6 +17,7 @@ const filesToCopy = [
17
17
  'WeaveDOMAPI.ts',
18
18
  'WeaveAPIClient.ts',
19
19
  'WeaveBackgroundAPI.ts',
20
+ 'WeaveCronAPI.ts',
20
21
  'global.ts',
21
22
  'app-template.js',
22
23
  'README.md',
@@ -1088,9 +1088,10 @@ customElements.define('smart-form-assistant', SmartFormAssistant);
1088
1088
  4. **Multi-Page Flows**: Use `this.background?.setPendingOperation()` for cross-page operations
1089
1089
  5. **Background Service**: `onBackgroundService()` runs immediately, before DOM attachment
1090
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
1091
+ 7. **Scheduled Tasks**: Use `this.cronTab()` to register cron jobs with standard cron syntax
1092
+ 8. **Shared Context**: Background and foreground share the same instance and state
1093
+ 9. **DOM Check**: Use `this.isConnected` to check if app is attached to DOM
1094
+ 10. **No User Interaction Required**: Apps can run and inject UI automatically
1094
1095
 
1095
1096
  ### Helper Methods (Available in `this`)
1096
1097
 
@@ -1118,6 +1119,18 @@ this.shadowRoot: ShadowRoot
1118
1119
 
1119
1120
  // Access background API for state persistence
1120
1121
  this.background: WeaveBackgroundAPI | null
1122
+
1123
+ // Access cron API for scheduled tasks
1124
+ this.cron: WeaveCronAPI | null
1125
+
1126
+ // Register a cron job (shorthand)
1127
+ await this.cronTab(expression: string, callback: () => void, jobName?: string): Promise<string | null>
1128
+
1129
+ // Unregister a cron job by name
1130
+ await this.cronUnregister(jobName: string): Promise<boolean>
1131
+
1132
+ // Unregister all cron jobs for this app
1133
+ await this.cronUnregisterAll(): Promise<number>
1121
1134
  ```
1122
1135
 
1123
1136
  ## State Persistence (Survive Page Reloads)
@@ -1372,6 +1385,255 @@ For long-term persistence, use `this.weaveAPI.appData.*` methods instead.
1372
1385
  - Don't forget to clear pending operations
1373
1386
  - Don't assume state will persist forever (5 min TTL)
1374
1387
 
1388
+ ## Scheduled Tasks (Cron Service)
1389
+
1390
+ ### Overview
1391
+
1392
+ Apps can register **scheduled tasks** that run on a timer using standard cron syntax. The browser extension acts as the master clock, ticking every second and notifying apps when their cron expressions match.
1393
+
1394
+ **Use cases:**
1395
+ - ⏰ **Periodic polling** - Check for updates every N seconds
1396
+ - 🔄 **Auto-refresh** - Update UI or data at intervals
1397
+ - 📊 **Monitoring** - Track metrics over time
1398
+ - 🔔 **Reminders** - Trigger notifications on schedule
1399
+
1400
+ ### Architecture
1401
+
1402
+ ```
1403
+ [Background Script: CronService] ← Master clock (ticks every second)
1404
+
1405
+ [Content Script: CronBridge] ← Routes tick messages
1406
+
1407
+ [Iframe: App] ← Receives CRON_TICK, executes callback
1408
+ ```
1409
+
1410
+ ### Registering a Cron Job
1411
+
1412
+ Use `this.cronTab()` to register a cron job:
1413
+
1414
+ ```typescript
1415
+ class MyApp extends WeaveBaseApp {
1416
+ constructor() {
1417
+ super({
1418
+ id: 'my-app',
1419
+ name: 'My App',
1420
+ version: '1.0.0',
1421
+ category: 'utility',
1422
+ description: 'App with scheduled tasks',
1423
+ tags: ['cron'],
1424
+ persistState: true, // Recommended for cron apps
1425
+ });
1426
+
1427
+ this.state = {
1428
+ tickCount: 0,
1429
+ cronEnabled: false
1430
+ };
1431
+ }
1432
+
1433
+ async onBackgroundService() {
1434
+ // Re-register cron if it was enabled before page reload
1435
+ if (this.state.cronEnabled) {
1436
+ await this.startCron();
1437
+ }
1438
+ }
1439
+
1440
+ async startCron() {
1441
+ // Register a cron job - every 5 seconds
1442
+ const jobId = await this.cronTab('*/5 * * * * *', this.handleTick, 'myTicker');
1443
+
1444
+ if (jobId) {
1445
+ console.log('Cron registered:', jobId);
1446
+ this.setState({ cronEnabled: true });
1447
+ }
1448
+ }
1449
+
1450
+ async stopCron() {
1451
+ await this.cronUnregisterAll();
1452
+ this.setState({ cronEnabled: false });
1453
+ }
1454
+
1455
+ // Cron callback - called every 5 seconds
1456
+ handleTick() {
1457
+ this.setState({ tickCount: this.state.tickCount + 1 });
1458
+ console.log('Tick!', this.state.tickCount);
1459
+
1460
+ // Update UI if app is open
1461
+ if (this.isConnected) {
1462
+ this.render();
1463
+ this.setupEventListeners();
1464
+ }
1465
+ }
1466
+ }
1467
+ ```
1468
+
1469
+ ### Cron Syntax
1470
+
1471
+ The cron service supports both **5-field** (standard) and **6-field** (with seconds) expressions:
1472
+
1473
+ #### 5-Field (Standard Cron)
1474
+ ```
1475
+ ┌───────────── minute (0-59)
1476
+ │ ┌───────────── hour (0-23)
1477
+ │ │ ┌───────────── day of month (1-31)
1478
+ │ │ │ ┌───────────── month (1-12)
1479
+ │ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
1480
+ │ │ │ │ │
1481
+ * * * * *
1482
+ ```
1483
+
1484
+ #### 6-Field (With Seconds)
1485
+ ```
1486
+ ┌───────────── second (0-59)
1487
+ │ ┌───────────── minute (0-59)
1488
+ │ │ ┌───────────── hour (0-23)
1489
+ │ │ │ ┌───────────── day of month (1-31)
1490
+ │ │ │ │ ┌───────────── month (1-12)
1491
+ │ │ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
1492
+ │ │ │ │ │ │
1493
+ * * * * * *
1494
+ ```
1495
+
1496
+ #### Special Characters
1497
+
1498
+ | Character | Description | Example |
1499
+ |-----------|-------------|---------|
1500
+ | `*` | Any value | `* * * * * *` = every second |
1501
+ | `,` | Value list | `0,30 * * * * *` = at 0 and 30 seconds |
1502
+ | `-` | Range | `0-10 * * * * *` = seconds 0-10 |
1503
+ | `/` | Step | `*/5 * * * * *` = every 5 seconds |
1504
+
1505
+ #### Common Examples
1506
+
1507
+ ```typescript
1508
+ // Every second
1509
+ await this.cronTab('* * * * * *', callback);
1510
+
1511
+ // Every 5 seconds
1512
+ await this.cronTab('*/5 * * * * *', callback);
1513
+
1514
+ // Every 10 seconds
1515
+ await this.cronTab('*/10 * * * * *', callback);
1516
+
1517
+ // Every 30 seconds
1518
+ await this.cronTab('0,30 * * * * *', callback);
1519
+
1520
+ // Every minute (at second 0)
1521
+ await this.cronTab('0 * * * * *', callback);
1522
+
1523
+ // Every 5 minutes
1524
+ await this.cronTab('0 */5 * * * *', callback);
1525
+
1526
+ // Every hour at minute 0
1527
+ await this.cronTab('0 0 * * * *', callback);
1528
+
1529
+ // Standard 5-field: every minute
1530
+ await this.cronTab('* * * * *', callback);
1531
+ ```
1532
+
1533
+ ### Cron API Methods
1534
+
1535
+ ```typescript
1536
+ // Register a cron job
1537
+ // Returns job ID if successful, null if failed
1538
+ await this.cronTab(
1539
+ cronExpression: string, // Cron expression (5 or 6 fields)
1540
+ callback: () => void, // Function to call on each tick
1541
+ jobName?: string // Optional unique name for the job
1542
+ ): Promise<string | null>
1543
+
1544
+ // Unregister a specific job by name
1545
+ await this.cronUnregister(jobName: string): Promise<boolean>
1546
+
1547
+ // Unregister all cron jobs for this app
1548
+ await this.cronUnregisterAll(): Promise<number>
1549
+
1550
+ // Access cron API directly (advanced)
1551
+ this.cron?.register(expression, callback, name)
1552
+ this.cron?.unregister(jobName)
1553
+ this.cron?.unregisterAll()
1554
+ this.cron?.list()
1555
+ ```
1556
+
1557
+ ### Persisting Cron State Across Page Reloads
1558
+
1559
+ Cron jobs are **not automatically re-registered** after a page reload. Use `persistState: true` and re-register in `onBackgroundService()`:
1560
+
1561
+ ```typescript
1562
+ class PersistentCronApp extends WeaveBaseApp {
1563
+ constructor() {
1564
+ super({
1565
+ id: 'persistent-cron',
1566
+ name: 'Persistent Cron',
1567
+ version: '1.0.0',
1568
+ category: 'utility',
1569
+ description: 'Cron that survives page reloads',
1570
+ tags: ['cron'],
1571
+ persistState: true, // ✅ Enable state persistence
1572
+ });
1573
+
1574
+ this.state = {
1575
+ cronEnabled: false,
1576
+ tickCount: 0
1577
+ };
1578
+ }
1579
+
1580
+ async onBackgroundService() {
1581
+ // ✅ Re-register cron if it was enabled before reload
1582
+ if (this.state.cronEnabled) {
1583
+ await this.cronTab('*/5 * * * * *', this.handleTick, 'ticker');
1584
+ }
1585
+ }
1586
+
1587
+ handleTick() {
1588
+ this.setState({ tickCount: this.state.tickCount + 1 });
1589
+ }
1590
+
1591
+ async toggleCron() {
1592
+ if (this.state.cronEnabled) {
1593
+ await this.cronUnregisterAll();
1594
+ this.setState({ cronEnabled: false });
1595
+ } else {
1596
+ await this.cronTab('*/5 * * * * *', this.handleTick, 'ticker');
1597
+ this.setState({ cronEnabled: true });
1598
+ }
1599
+ }
1600
+ }
1601
+ ```
1602
+
1603
+ ### Best Practices
1604
+
1605
+ #### ✅ DO:
1606
+ - Use `persistState: true` to remember cron state across reloads
1607
+ - Re-register cron jobs in `onBackgroundService()` if they were enabled
1608
+ - Give jobs meaningful names for easier management
1609
+ - Use appropriate intervals (don't poll every second unless necessary)
1610
+ - Update UI only if `this.isConnected` (app is open)
1611
+ - Clean up cron jobs in `cleanup()` method
1612
+
1613
+ #### ❌ DON'T:
1614
+ - Don't register cron jobs in the constructor (use `onBackgroundService()`)
1615
+ - Don't use very short intervals without good reason (battery/performance)
1616
+ - Don't assume cron jobs survive page reloads (they don't)
1617
+ - Don't forget to unregister jobs when no longer needed
1618
+
1619
+ ### Cleanup
1620
+
1621
+ Cron jobs are automatically cleaned up when:
1622
+ - The tab is closed
1623
+ - The app explicitly unregisters them
1624
+ - The extension is reloaded
1625
+
1626
+ For manual cleanup:
1627
+
1628
+ ```typescript
1629
+ class MyApp extends WeaveBaseApp {
1630
+ protected cleanup(): void {
1631
+ // Clean up cron jobs when app is removed from DOM
1632
+ this.cronUnregisterAll();
1633
+ }
1634
+ }
1635
+ ```
1636
+
1375
1637
  ## Weave Backend API
1376
1638
 
1377
1639
  ### ⚠️ CRITICAL: API Access Restrictions