@metamask/snaps-controllers 9.15.0 → 9.17.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/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [9.17.0]
11
+
12
+ ### Added
13
+
14
+ - Added support for non-recurring cronjobs via `snap_scheduleBackgroundEvent` ([#2941](https://github.com/MetaMask/snaps/pull/2941))
15
+
16
+ ### Changed
17
+
18
+ - Bump MetaMask dependencies ([#2946](https://github.com/MetaMask/snaps/pull/2946), [#3007](https://github.com/MetaMask/snaps/pull/3007), [#2999](https://github.com/MetaMask/snaps/pull/2999), [#3003](https://github.com/MetaMask/snaps/pull/3003), [#2991](https://github.com/MetaMask/snaps/pull/2991), [#2989](https://github.com/MetaMask/snaps/pull/2989))
19
+ - Cache snap state in memory for improved performance ([#2980](https://github.com/MetaMask/snaps/pull/2980))
20
+
21
+ ### Fixed
22
+
23
+ - Stop storing messenger manually in `CronjobController` ([#3006](https://github.com/MetaMask/snaps/pull/3006))
24
+
25
+ ## [9.16.0]
26
+
27
+ ### Added
28
+
29
+ - Add support for `onSettingsPage` export ([#2911](https://github.com/MetaMask/snaps/pull/2911))
30
+
31
+ ### Fixed
32
+
33
+ - Use `BigInt` for processing insight chain IDs ([#2935](https://github.com/MetaMask/snaps/pull/2935))
34
+
10
35
  ## [9.15.0]
11
36
 
12
37
  ### Added
@@ -580,7 +605,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
580
605
  - The version of the package no longer needs to match the version of all other
581
606
  MetaMask Snaps packages.
582
607
 
583
- [Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.15.0...HEAD
608
+ [Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.17.0...HEAD
609
+ [9.17.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.16.0...@metamask/snaps-controllers@9.17.0
610
+ [9.16.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.15.0...@metamask/snaps-controllers@9.16.0
584
611
  [9.15.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.14.0...@metamask/snaps-controllers@9.15.0
585
612
  [9.14.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.13.0...@metamask/snaps-controllers@9.14.0
586
613
  [9.13.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.12.0...@metamask/snaps-controllers@9.13.0
@@ -10,13 +10,16 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _CronjobController_messenger, _CronjobController_dailyTimer, _CronjobController_timers, _CronjobController_snapIds;
13
+ var _CronjobController_instances, _CronjobController_dailyTimer, _CronjobController_timers, _CronjobController_snapIds, _CronjobController_getAllJobs, _CronjobController_getSnapJobs, _CronjobController_schedule, _CronjobController_executeCronjob, _CronjobController_setUpBackgroundEvent, _CronjobController_updateJobLastRunState, _CronjobController_rescheduleBackgroundEvents;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.CronjobController = exports.DAILY_TIMEOUT = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
17
17
  const snaps_rpc_methods_1 = require("@metamask/snaps-rpc-methods");
18
18
  const snaps_utils_1 = require("@metamask/snaps-utils");
19
19
  const utils_1 = require("@metamask/utils");
20
+ const immer_1 = require("immer");
21
+ const luxon_1 = require("luxon");
22
+ const nanoid_1 = require("nanoid");
20
23
  const __1 = require("../index.cjs");
21
24
  const Timer_1 = require("../snaps/Timer.cjs");
22
25
  exports.DAILY_TIMEOUT = (0, utils_1.inMilliseconds)(24, utils_1.Duration.Hour);
@@ -31,62 +34,43 @@ class CronjobController extends base_controller_1.BaseController {
31
34
  messenger,
32
35
  metadata: {
33
36
  jobs: { persist: true, anonymous: false },
37
+ events: { persist: true, anonymous: false },
34
38
  },
35
39
  name: controllerName,
36
40
  state: {
37
41
  jobs: {},
42
+ events: {},
38
43
  ...state,
39
44
  },
40
45
  });
41
- _CronjobController_messenger.set(this, void 0);
46
+ _CronjobController_instances.add(this);
42
47
  _CronjobController_dailyTimer.set(this, void 0);
43
48
  _CronjobController_timers.set(this, void 0);
44
49
  // Mapping from jobId to snapId
45
50
  _CronjobController_snapIds.set(this, void 0);
46
51
  __classPrivateFieldSet(this, _CronjobController_timers, new Map(), "f");
47
52
  __classPrivateFieldSet(this, _CronjobController_snapIds, new Map(), "f");
48
- __classPrivateFieldSet(this, _CronjobController_messenger, messenger, "f");
49
53
  this._handleSnapRegisterEvent = this._handleSnapRegisterEvent.bind(this);
50
54
  this._handleSnapUnregisterEvent =
51
55
  this._handleSnapUnregisterEvent.bind(this);
52
56
  this._handleEventSnapUpdated = this._handleEventSnapUpdated.bind(this);
57
+ this._handleSnapDisabledEvent = this._handleSnapDisabledEvent.bind(this);
58
+ this._handleSnapEnabledEvent = this._handleSnapEnabledEvent.bind(this);
53
59
  // Subscribe to Snap events
54
60
  /* eslint-disable @typescript-eslint/unbound-method */
55
61
  this.messagingSystem.subscribe('SnapController:snapInstalled', this._handleSnapRegisterEvent);
56
62
  this.messagingSystem.subscribe('SnapController:snapUninstalled', this._handleSnapUnregisterEvent);
57
- this.messagingSystem.subscribe('SnapController:snapEnabled', this._handleSnapRegisterEvent);
58
- this.messagingSystem.subscribe('SnapController:snapDisabled', this._handleSnapUnregisterEvent);
63
+ this.messagingSystem.subscribe('SnapController:snapEnabled', this._handleSnapEnabledEvent);
64
+ this.messagingSystem.subscribe('SnapController:snapDisabled', this._handleSnapDisabledEvent);
59
65
  this.messagingSystem.subscribe('SnapController:snapUpdated', this._handleEventSnapUpdated);
60
66
  /* eslint-enable @typescript-eslint/unbound-method */
67
+ this.messagingSystem.registerActionHandler(`${controllerName}:scheduleBackgroundEvent`, (...args) => this.scheduleBackgroundEvent(...args));
68
+ this.messagingSystem.registerActionHandler(`${controllerName}:cancelBackgroundEvent`, (...args) => this.cancelBackgroundEvent(...args));
69
+ this.messagingSystem.registerActionHandler(`${controllerName}:getBackgroundEvents`, (...args) => this.getBackgroundEvents(...args));
61
70
  this.dailyCheckIn().catch((error) => {
62
71
  (0, snaps_utils_1.logError)(error);
63
72
  });
64
- }
65
- /**
66
- * Retrieve all cronjob specifications for all runnable snaps.
67
- *
68
- * @returns Array of Cronjob specifications.
69
- */
70
- getAllJobs() {
71
- const snaps = this.messagingSystem.call('SnapController:getAll');
72
- const filteredSnaps = (0, __1.getRunnableSnaps)(snaps);
73
- const jobs = filteredSnaps.map((snap) => this.getSnapJobs(snap.id));
74
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
75
- return jobs.flat().filter((job) => job !== undefined);
76
- }
77
- /**
78
- * Retrieve all Cronjob specifications for a Snap.
79
- *
80
- * @param snapId - ID of a Snap.
81
- * @returns Array of Cronjob specifications.
82
- */
83
- getSnapJobs(snapId) {
84
- const permissions = __classPrivateFieldGet(this, _CronjobController_messenger, "f").call('PermissionController:getPermissions', snapId);
85
- const permission = permissions?.[snaps_rpc_methods_1.SnapEndowments.Cronjob];
86
- const definitions = (0, snaps_rpc_methods_1.getCronjobCaveatJobs)(permission);
87
- return definitions?.map((definition, idx) => {
88
- return { ...definition, id: `${snapId}-${idx}`, snapId };
89
- });
73
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_rescheduleBackgroundEvents).call(this, Object.values(this.state.events));
90
74
  }
91
75
  /**
92
76
  * Register cron jobs for a given snap by getting specification from a permission caveats.
@@ -95,99 +79,99 @@ class CronjobController extends base_controller_1.BaseController {
95
79
  * @param snapId - ID of a snap.
96
80
  */
97
81
  register(snapId) {
98
- const jobs = this.getSnapJobs(snapId);
99
- jobs?.forEach((job) => this.schedule(job));
82
+ const jobs = __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_getSnapJobs).call(this, snapId);
83
+ jobs?.forEach((job) => __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_schedule).call(this, job));
100
84
  }
101
85
  /**
102
- * Schedule a new job.
103
- * This will interpret the cron expression and tell the timer to execute the job
104
- * at the next suitable point in time.
105
- * Job last run state will be initialized afterwards.
86
+ * Schedule a background event.
106
87
  *
107
- * Note: Schedule will be skipped if the job's execution time is too far in the future and
108
- * will be revisited on a daily check.
109
- *
110
- * @param job - Cronjob specification.
88
+ * @param backgroundEventWithoutId - Background event.
89
+ * @returns An id representing the background event.
111
90
  */
112
- schedule(job) {
113
- if (__classPrivateFieldGet(this, _CronjobController_timers, "f").has(job.id)) {
114
- return;
115
- }
116
- const parsed = (0, snaps_utils_1.parseCronExpression)(job.expression);
117
- const next = parsed.next();
118
- const now = new Date();
119
- const ms = next.getTime() - now.getTime();
120
- // Don't schedule this job yet as it is too far in the future
121
- if (ms > exports.DAILY_TIMEOUT) {
122
- return;
123
- }
124
- const timer = new Timer_1.Timer(ms);
125
- timer.start(() => {
126
- this.executeCronjob(job).catch((error) => {
127
- // TODO: Decide how to handle errors.
128
- (0, snaps_utils_1.logError)(error);
129
- });
130
- __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(job.id);
131
- this.schedule(job);
91
+ scheduleBackgroundEvent(backgroundEventWithoutId) {
92
+ // Remove millisecond precision and convert to UTC.
93
+ const scheduledAt = luxon_1.DateTime.fromJSDate(new Date())
94
+ .toUTC()
95
+ .startOf('second')
96
+ .toISO({
97
+ suppressMilliseconds: true,
132
98
  });
133
- if (!this.state.jobs[job.id]?.lastRun) {
134
- this.updateJobLastRunState(job.id, 0); // 0 for init, never ran actually
135
- }
136
- __classPrivateFieldGet(this, _CronjobController_timers, "f").set(job.id, timer);
137
- __classPrivateFieldGet(this, _CronjobController_snapIds, "f").set(job.id, job.snapId);
99
+ (0, utils_1.assert)(scheduledAt);
100
+ const event = {
101
+ ...backgroundEventWithoutId,
102
+ id: (0, nanoid_1.nanoid)(),
103
+ scheduledAt,
104
+ };
105
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_setUpBackgroundEvent).call(this, event);
106
+ this.update((state) => {
107
+ state.events[event.id] = (0, immer_1.castDraft)(event);
108
+ });
109
+ return event.id;
138
110
  }
139
111
  /**
140
- * Execute job.
112
+ * Cancel a background event.
141
113
  *
142
- * @param job - Cronjob specification.
114
+ * @param origin - The origin making the cancel call.
115
+ * @param id - The id of the background event to cancel.
116
+ * @throws If the event does not exist.
143
117
  */
144
- async executeCronjob(job) {
145
- this.updateJobLastRunState(job.id, Date.now());
146
- await __classPrivateFieldGet(this, _CronjobController_messenger, "f").call('SnapController:handleRequest', {
147
- snapId: job.snapId,
148
- origin: '',
149
- handler: snaps_utils_1.HandlerType.OnCronjob,
150
- request: job.request,
118
+ cancelBackgroundEvent(origin, id) {
119
+ (0, utils_1.assert)(this.state.events[id], `A background event with the id of "${id}" does not exist.`);
120
+ (0, utils_1.assert)(this.state.events[id].snapId === origin, 'Only the origin that scheduled this event can cancel it.');
121
+ const timer = __classPrivateFieldGet(this, _CronjobController_timers, "f").get(id);
122
+ timer?.cancel();
123
+ __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(id);
124
+ __classPrivateFieldGet(this, _CronjobController_snapIds, "f").delete(id);
125
+ this.update((state) => {
126
+ delete state.events[id];
151
127
  });
152
128
  }
153
129
  /**
154
- * Unregister all jobs related to the given snapId.
130
+ * Get a list of a Snap's background events.
131
+ *
132
+ * @param snapId - The id of the Snap to fetch background events for.
133
+ * @returns An array of background events.
134
+ */
135
+ getBackgroundEvents(snapId) {
136
+ return Object.values(this.state.events).filter((snapEvent) => snapEvent.snapId === snapId);
137
+ }
138
+ /**
139
+ * Unregister all jobs and background events related to the given snapId.
155
140
  *
156
141
  * @param snapId - ID of a snap.
142
+ * @param skipEvents - Whether the unregistration process should skip scheduled background events.
157
143
  */
158
- unregister(snapId) {
144
+ unregister(snapId, skipEvents = false) {
159
145
  const jobs = [...__classPrivateFieldGet(this, _CronjobController_snapIds, "f").entries()].filter(([_, jobSnapId]) => jobSnapId === snapId);
160
146
  if (jobs.length) {
147
+ const eventIds = [];
161
148
  jobs.forEach(([id]) => {
162
149
  const timer = __classPrivateFieldGet(this, _CronjobController_timers, "f").get(id);
163
150
  if (timer) {
164
151
  timer.cancel();
165
152
  __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(id);
166
153
  __classPrivateFieldGet(this, _CronjobController_snapIds, "f").delete(id);
154
+ if (!skipEvents && this.state.events[id]) {
155
+ eventIds.push(id);
156
+ }
167
157
  }
168
158
  });
159
+ if (eventIds.length > 0) {
160
+ this.update((state) => {
161
+ eventIds.forEach((id) => {
162
+ delete state.events[id];
163
+ });
164
+ });
165
+ }
169
166
  }
170
167
  }
171
- /**
172
- * Update time of a last run for the Cronjob specified by ID.
173
- *
174
- * @param jobId - ID of a cron job.
175
- * @param lastRun - Unix timestamp when the job was last ran.
176
- */
177
- updateJobLastRunState(jobId, lastRun) {
178
- this.update((state) => {
179
- state.jobs[jobId] = {
180
- lastRun,
181
- };
182
- });
183
- }
184
168
  /**
185
169
  * Runs every 24 hours to check if new jobs need to be scheduled.
186
170
  *
187
171
  * This is necessary for longer running jobs that execute with more than 24 hours between them.
188
172
  */
189
173
  async dailyCheckIn() {
190
- const jobs = this.getAllJobs();
174
+ const jobs = __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_getAllJobs).call(this);
191
175
  for (const job of jobs) {
192
176
  const parsed = (0, snaps_utils_1.parseCronExpression)(job.expression);
193
177
  const lastRun = this.state.jobs[job.id]?.lastRun;
@@ -195,10 +179,10 @@ class CronjobController extends base_controller_1.BaseController {
195
179
  if (lastRun !== undefined &&
196
180
  parsed.hasPrev() &&
197
181
  parsed.prev().getTime() > lastRun) {
198
- await this.executeCronjob(job);
182
+ await __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_executeCronjob).call(this, job);
199
183
  }
200
184
  // Try scheduling, will fail if an existing scheduled job is found
201
- this.schedule(job);
185
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_schedule).call(this, job);
202
186
  }
203
187
  __classPrivateFieldSet(this, _CronjobController_dailyTimer, new Timer_1.Timer(exports.DAILY_TIMEOUT), "f");
204
188
  __classPrivateFieldGet(this, _CronjobController_dailyTimer, "f").start(() => {
@@ -216,13 +200,11 @@ class CronjobController extends base_controller_1.BaseController {
216
200
  /* eslint-disable @typescript-eslint/unbound-method */
217
201
  this.messagingSystem.unsubscribe('SnapController:snapInstalled', this._handleSnapRegisterEvent);
218
202
  this.messagingSystem.unsubscribe('SnapController:snapUninstalled', this._handleSnapUnregisterEvent);
219
- this.messagingSystem.unsubscribe('SnapController:snapEnabled', this._handleSnapRegisterEvent);
220
- this.messagingSystem.unsubscribe('SnapController:snapDisabled', this._handleSnapUnregisterEvent);
203
+ this.messagingSystem.unsubscribe('SnapController:snapEnabled', this._handleSnapEnabledEvent);
204
+ this.messagingSystem.unsubscribe('SnapController:snapDisabled', this._handleSnapDisabledEvent);
221
205
  this.messagingSystem.unsubscribe('SnapController:snapUpdated', this._handleEventSnapUpdated);
222
206
  /* eslint-enable @typescript-eslint/unbound-method */
223
- __classPrivateFieldGet(this, _CronjobController_snapIds, "f").forEach((snapId) => {
224
- this.unregister(snapId);
225
- });
207
+ __classPrivateFieldGet(this, _CronjobController_snapIds, "f").forEach((snapId) => this.unregister(snapId));
226
208
  }
227
209
  /**
228
210
  * Handle events that should cause cronjobs to be registered.
@@ -233,13 +215,32 @@ class CronjobController extends base_controller_1.BaseController {
233
215
  this.register(snap.id);
234
216
  }
235
217
  /**
236
- * Handle events that should cause cronjobs to be unregistered.
218
+ * Handle events that could cause cronjobs to be registered
219
+ * and for background events to be rescheduled.
220
+ *
221
+ * @param snap - Basic Snap information.
222
+ */
223
+ _handleSnapEnabledEvent(snap) {
224
+ const events = this.getBackgroundEvents(snap.id);
225
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_rescheduleBackgroundEvents).call(this, events);
226
+ this.register(snap.id);
227
+ }
228
+ /**
229
+ * Handle events that should cause cronjobs and background events to be unregistered.
237
230
  *
238
231
  * @param snap - Basic Snap information.
239
232
  */
240
233
  _handleSnapUnregisterEvent(snap) {
241
234
  this.unregister(snap.id);
242
235
  }
236
+ /**
237
+ * Handle events that should cause cronjobs and background events to be unregistered.
238
+ *
239
+ * @param snap - Basic Snap information.
240
+ */
241
+ _handleSnapDisabledEvent(snap) {
242
+ this.unregister(snap.id, true);
243
+ }
243
244
  /**
244
245
  * Handle cron jobs on 'snapUpdated' event.
245
246
  *
@@ -251,5 +252,107 @@ class CronjobController extends base_controller_1.BaseController {
251
252
  }
252
253
  }
253
254
  exports.CronjobController = CronjobController;
254
- _CronjobController_messenger = new WeakMap(), _CronjobController_dailyTimer = new WeakMap(), _CronjobController_timers = new WeakMap(), _CronjobController_snapIds = new WeakMap();
255
+ _CronjobController_dailyTimer = new WeakMap(), _CronjobController_timers = new WeakMap(), _CronjobController_snapIds = new WeakMap(), _CronjobController_instances = new WeakSet(), _CronjobController_getAllJobs = function _CronjobController_getAllJobs() {
256
+ const snaps = this.messagingSystem.call('SnapController:getAll');
257
+ const filteredSnaps = (0, __1.getRunnableSnaps)(snaps);
258
+ const jobs = filteredSnaps.map((snap) => __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_getSnapJobs).call(this, snap.id));
259
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
260
+ return jobs.flat().filter((job) => job !== undefined);
261
+ }, _CronjobController_getSnapJobs = function _CronjobController_getSnapJobs(snapId) {
262
+ const permissions = this.messagingSystem.call('PermissionController:getPermissions', snapId);
263
+ const permission = permissions?.[snaps_rpc_methods_1.SnapEndowments.Cronjob];
264
+ const definitions = (0, snaps_rpc_methods_1.getCronjobCaveatJobs)(permission);
265
+ return definitions?.map((definition, idx) => {
266
+ return { ...definition, id: `${snapId}-${idx}`, snapId };
267
+ });
268
+ }, _CronjobController_schedule = function _CronjobController_schedule(job) {
269
+ if (__classPrivateFieldGet(this, _CronjobController_timers, "f").has(job.id)) {
270
+ return;
271
+ }
272
+ const parsed = (0, snaps_utils_1.parseCronExpression)(job.expression);
273
+ const next = parsed.next();
274
+ const now = new Date();
275
+ const ms = next.getTime() - now.getTime();
276
+ // Don't schedule this job yet as it is too far in the future
277
+ if (ms > exports.DAILY_TIMEOUT) {
278
+ return;
279
+ }
280
+ const timer = new Timer_1.Timer(ms);
281
+ timer.start(() => {
282
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_executeCronjob).call(this, job).catch((error) => {
283
+ // TODO: Decide how to handle errors.
284
+ (0, snaps_utils_1.logError)(error);
285
+ });
286
+ __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(job.id);
287
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_schedule).call(this, job);
288
+ });
289
+ if (!this.state.jobs[job.id]?.lastRun) {
290
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_updateJobLastRunState).call(this, job.id, 0); // 0 for init, never ran actually
291
+ }
292
+ __classPrivateFieldGet(this, _CronjobController_timers, "f").set(job.id, timer);
293
+ __classPrivateFieldGet(this, _CronjobController_snapIds, "f").set(job.id, job.snapId);
294
+ }, _CronjobController_executeCronjob =
295
+ /**
296
+ * Execute job.
297
+ *
298
+ * @param job - Cronjob specification.
299
+ */
300
+ async function _CronjobController_executeCronjob(job) {
301
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_updateJobLastRunState).call(this, job.id, Date.now());
302
+ await this.messagingSystem.call('SnapController:handleRequest', {
303
+ snapId: job.snapId,
304
+ origin: '',
305
+ handler: snaps_utils_1.HandlerType.OnCronjob,
306
+ request: job.request,
307
+ });
308
+ }, _CronjobController_setUpBackgroundEvent = function _CronjobController_setUpBackgroundEvent(event) {
309
+ const date = new Date(event.date);
310
+ const now = new Date();
311
+ const ms = date.getTime() - now.getTime();
312
+ if (ms <= 0) {
313
+ throw new Error('Cannot schedule an event in the past.');
314
+ }
315
+ const timer = new Timer_1.Timer(ms);
316
+ timer.start(() => {
317
+ this.messagingSystem
318
+ .call('SnapController:handleRequest', {
319
+ snapId: event.snapId,
320
+ origin: '',
321
+ handler: snaps_utils_1.HandlerType.OnCronjob,
322
+ request: event.request,
323
+ })
324
+ .catch((error) => {
325
+ (0, snaps_utils_1.logError)(error);
326
+ });
327
+ __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(event.id);
328
+ __classPrivateFieldGet(this, _CronjobController_snapIds, "f").delete(event.id);
329
+ this.update((state) => {
330
+ delete state.events[event.id];
331
+ });
332
+ });
333
+ __classPrivateFieldGet(this, _CronjobController_timers, "f").set(event.id, timer);
334
+ __classPrivateFieldGet(this, _CronjobController_snapIds, "f").set(event.id, event.snapId);
335
+ }, _CronjobController_updateJobLastRunState = function _CronjobController_updateJobLastRunState(jobId, lastRun) {
336
+ this.update((state) => {
337
+ state.jobs[jobId] = {
338
+ lastRun,
339
+ };
340
+ });
341
+ }, _CronjobController_rescheduleBackgroundEvents = function _CronjobController_rescheduleBackgroundEvents(backgroundEvents) {
342
+ for (const snapEvent of backgroundEvents) {
343
+ const { date } = snapEvent;
344
+ const now = new Date();
345
+ const then = new Date(date);
346
+ if (then.getTime() < now.getTime()) {
347
+ // Remove expired events from state
348
+ this.update((state) => {
349
+ delete state.events[snapEvent.id];
350
+ });
351
+ (0, snaps_utils_1.logWarning)(`Background event with id "${snapEvent.id}" not scheduled as its date has expired.`);
352
+ }
353
+ else {
354
+ __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_setUpBackgroundEvent).call(this, snapEvent);
355
+ }
356
+ }
357
+ };
255
358
  //# sourceMappingURL=CronjobController.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CronjobController.cjs","sourceRoot":"","sources":["../../src/cronjob/CronjobController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RestrictedControllerMessenger,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { GetPermissions } from '@metamask/permission-controller';\nimport {\n getCronjobCaveatJobs,\n SnapEndowments,\n} from '@metamask/snaps-rpc-methods';\nimport type { SnapId } from '@metamask/snaps-sdk';\nimport type {\n TruncatedSnap,\n CronjobSpecification,\n} from '@metamask/snaps-utils';\nimport {\n HandlerType,\n parseCronExpression,\n logError,\n} from '@metamask/snaps-utils';\nimport { Duration, inMilliseconds } from '@metamask/utils';\n\nimport type {\n GetAllSnaps,\n HandleSnapRequest,\n SnapDisabled,\n SnapEnabled,\n SnapInstalled,\n SnapUninstalled,\n SnapUpdated,\n} from '..';\nimport { getRunnableSnaps } from '..';\nimport { Timer } from '../snaps/Timer';\n\nexport type CronjobControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n CronjobControllerState\n>;\nexport type CronjobControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n CronjobControllerState\n>;\nexport type CronjobControllerActions =\n | GetAllSnaps\n | HandleSnapRequest\n | GetPermissions\n | CronjobControllerGetStateAction;\n\nexport type CronjobControllerEvents =\n | SnapInstalled\n | SnapUninstalled\n | SnapUpdated\n | SnapEnabled\n | SnapDisabled\n | CronjobControllerStateChangeEvent;\n\nexport type CronjobControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n CronjobControllerActions,\n CronjobControllerEvents,\n CronjobControllerActions['type'],\n CronjobControllerEvents['type']\n>;\n\nexport const DAILY_TIMEOUT = inMilliseconds(24, Duration.Hour);\n\nexport type CronjobControllerArgs = {\n messenger: CronjobControllerMessenger;\n /**\n * Persisted state that will be used for rehydration.\n */\n state?: CronjobControllerState;\n};\n\nexport type Cronjob = {\n timer?: Timer;\n id: string;\n snapId: SnapId;\n} & CronjobSpecification;\n\nexport type StoredJobInformation = {\n lastRun: number;\n};\n\nexport type CronjobControllerState = {\n jobs: Record<string, StoredJobInformation>;\n};\n\nconst controllerName = 'CronjobController';\n\n/**\n * Use this controller to register and schedule periodically executed jobs\n * using RPC method hooks.\n */\nexport class CronjobController extends BaseController<\n typeof controllerName,\n CronjobControllerState,\n CronjobControllerMessenger\n> {\n #messenger: CronjobControllerMessenger;\n\n #dailyTimer!: Timer;\n\n #timers: Map<string, Timer>;\n\n // Mapping from jobId to snapId\n #snapIds: Map<string, string>;\n\n constructor({ messenger, state }: CronjobControllerArgs) {\n super({\n messenger,\n metadata: {\n jobs: { persist: true, anonymous: false },\n },\n name: controllerName,\n state: {\n jobs: {},\n ...state,\n },\n });\n this.#timers = new Map();\n this.#snapIds = new Map();\n this.#messenger = messenger;\n\n this._handleSnapRegisterEvent = this._handleSnapRegisterEvent.bind(this);\n this._handleSnapUnregisterEvent =\n this._handleSnapUnregisterEvent.bind(this);\n this._handleEventSnapUpdated = this._handleEventSnapUpdated.bind(this);\n\n // Subscribe to Snap events\n /* eslint-disable @typescript-eslint/unbound-method */\n this.messagingSystem.subscribe(\n 'SnapController:snapInstalled',\n this._handleSnapRegisterEvent,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapUninstalled',\n this._handleSnapUnregisterEvent,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapEnabled',\n this._handleSnapRegisterEvent,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapDisabled',\n this._handleSnapUnregisterEvent,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapUpdated',\n this._handleEventSnapUpdated,\n );\n /* eslint-enable @typescript-eslint/unbound-method */\n\n this.dailyCheckIn().catch((error) => {\n logError(error);\n });\n }\n\n /**\n * Retrieve all cronjob specifications for all runnable snaps.\n *\n * @returns Array of Cronjob specifications.\n */\n private getAllJobs(): Cronjob[] {\n const snaps = this.messagingSystem.call('SnapController:getAll');\n const filteredSnaps = getRunnableSnaps(snaps);\n\n const jobs = filteredSnaps.map((snap) => this.getSnapJobs(snap.id));\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n return jobs.flat().filter((job) => job !== undefined) as Cronjob[];\n }\n\n /**\n * Retrieve all Cronjob specifications for a Snap.\n *\n * @param snapId - ID of a Snap.\n * @returns Array of Cronjob specifications.\n */\n private getSnapJobs(snapId: SnapId): Cronjob[] | undefined {\n const permissions = this.#messenger.call(\n 'PermissionController:getPermissions',\n snapId,\n );\n\n const permission = permissions?.[SnapEndowments.Cronjob];\n const definitions = getCronjobCaveatJobs(permission);\n\n return definitions?.map((definition, idx) => {\n return { ...definition, id: `${snapId}-${idx}`, snapId };\n });\n }\n\n /**\n * Register cron jobs for a given snap by getting specification from a permission caveats.\n * Once registered, each job will be scheduled.\n *\n * @param snapId - ID of a snap.\n */\n register(snapId: SnapId) {\n const jobs = this.getSnapJobs(snapId);\n jobs?.forEach((job) => this.schedule(job));\n }\n\n /**\n * Schedule a new job.\n * This will interpret the cron expression and tell the timer to execute the job\n * at the next suitable point in time.\n * Job last run state will be initialized afterwards.\n *\n * Note: Schedule will be skipped if the job's execution time is too far in the future and\n * will be revisited on a daily check.\n *\n * @param job - Cronjob specification.\n */\n private schedule(job: Cronjob) {\n if (this.#timers.has(job.id)) {\n return;\n }\n\n const parsed = parseCronExpression(job.expression);\n const next = parsed.next();\n const now = new Date();\n const ms = next.getTime() - now.getTime();\n\n // Don't schedule this job yet as it is too far in the future\n if (ms > DAILY_TIMEOUT) {\n return;\n }\n\n const timer = new Timer(ms);\n timer.start(() => {\n this.executeCronjob(job).catch((error) => {\n // TODO: Decide how to handle errors.\n logError(error);\n });\n\n this.#timers.delete(job.id);\n this.schedule(job);\n });\n\n if (!this.state.jobs[job.id]?.lastRun) {\n this.updateJobLastRunState(job.id, 0); // 0 for init, never ran actually\n }\n\n this.#timers.set(job.id, timer);\n this.#snapIds.set(job.id, job.snapId);\n }\n\n /**\n * Execute job.\n *\n * @param job - Cronjob specification.\n */\n private async executeCronjob(job: Cronjob) {\n this.updateJobLastRunState(job.id, Date.now());\n await this.#messenger.call('SnapController:handleRequest', {\n snapId: job.snapId,\n origin: '',\n handler: HandlerType.OnCronjob,\n request: job.request,\n });\n }\n\n /**\n * Unregister all jobs related to the given snapId.\n *\n * @param snapId - ID of a snap.\n */\n unregister(snapId: string) {\n const jobs = [...this.#snapIds.entries()].filter(\n ([_, jobSnapId]) => jobSnapId === snapId,\n );\n\n if (jobs.length) {\n jobs.forEach(([id]) => {\n const timer = this.#timers.get(id);\n if (timer) {\n timer.cancel();\n this.#timers.delete(id);\n this.#snapIds.delete(id);\n }\n });\n }\n }\n\n /**\n * Update time of a last run for the Cronjob specified by ID.\n *\n * @param jobId - ID of a cron job.\n * @param lastRun - Unix timestamp when the job was last ran.\n */\n private updateJobLastRunState(jobId: string, lastRun: number) {\n this.update((state) => {\n state.jobs[jobId] = {\n lastRun,\n };\n });\n }\n\n /**\n * Runs every 24 hours to check if new jobs need to be scheduled.\n *\n * This is necessary for longer running jobs that execute with more than 24 hours between them.\n */\n async dailyCheckIn() {\n const jobs = this.getAllJobs();\n\n for (const job of jobs) {\n const parsed = parseCronExpression(job.expression);\n const lastRun = this.state.jobs[job.id]?.lastRun;\n // If a job was supposed to run while we were shut down but wasn't we run it now\n if (\n lastRun !== undefined &&\n parsed.hasPrev() &&\n parsed.prev().getTime() > lastRun\n ) {\n await this.executeCronjob(job);\n }\n\n // Try scheduling, will fail if an existing scheduled job is found\n this.schedule(job);\n }\n\n this.#dailyTimer = new Timer(DAILY_TIMEOUT);\n this.#dailyTimer.start(() => {\n this.dailyCheckIn().catch((error) => {\n // TODO: Decide how to handle errors.\n logError(error);\n });\n });\n }\n\n /**\n * Run controller teardown process and unsubscribe from Snap events.\n */\n destroy() {\n super.destroy();\n\n /* eslint-disable @typescript-eslint/unbound-method */\n this.messagingSystem.unsubscribe(\n 'SnapController:snapInstalled',\n this._handleSnapRegisterEvent,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapUninstalled',\n this._handleSnapUnregisterEvent,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapEnabled',\n this._handleSnapRegisterEvent,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapDisabled',\n this._handleSnapUnregisterEvent,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapUpdated',\n this._handleEventSnapUpdated,\n );\n /* eslint-enable @typescript-eslint/unbound-method */\n\n this.#snapIds.forEach((snapId) => {\n this.unregister(snapId);\n });\n }\n\n /**\n * Handle events that should cause cronjobs to be registered.\n *\n * @param snap - Basic Snap information.\n */\n private _handleSnapRegisterEvent(snap: TruncatedSnap) {\n this.register(snap.id);\n }\n\n /**\n * Handle events that should cause cronjobs to be unregistered.\n *\n * @param snap - Basic Snap information.\n */\n private _handleSnapUnregisterEvent(snap: TruncatedSnap) {\n this.unregister(snap.id);\n }\n\n /**\n * Handle cron jobs on 'snapUpdated' event.\n *\n * @param snap - Basic Snap information.\n */\n private _handleEventSnapUpdated(snap: TruncatedSnap) {\n this.unregister(snap.id);\n this.register(snap.id);\n }\n}\n"]}
1
+ {"version":3,"file":"CronjobController.cjs","sourceRoot":"","sources":["../../src/cronjob/CronjobController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RestrictedControllerMessenger,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { GetPermissions } from '@metamask/permission-controller';\nimport {\n getCronjobCaveatJobs,\n SnapEndowments,\n} from '@metamask/snaps-rpc-methods';\nimport type { BackgroundEvent, SnapId } from '@metamask/snaps-sdk';\nimport type {\n TruncatedSnap,\n CronjobSpecification,\n} from '@metamask/snaps-utils';\nimport {\n HandlerType,\n parseCronExpression,\n logError,\n logWarning,\n} from '@metamask/snaps-utils';\nimport { assert, Duration, inMilliseconds } from '@metamask/utils';\nimport { castDraft } from 'immer';\nimport { DateTime } from 'luxon';\nimport { nanoid } from 'nanoid';\n\nimport type {\n GetAllSnaps,\n HandleSnapRequest,\n SnapDisabled,\n SnapEnabled,\n SnapInstalled,\n SnapUninstalled,\n SnapUpdated,\n} from '..';\nimport { getRunnableSnaps } from '..';\nimport { Timer } from '../snaps/Timer';\n\nexport type CronjobControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n CronjobControllerState\n>;\nexport type CronjobControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n CronjobControllerState\n>;\n\nexport type ScheduleBackgroundEvent = {\n type: `${typeof controllerName}:scheduleBackgroundEvent`;\n handler: CronjobController['scheduleBackgroundEvent'];\n};\n\nexport type CancelBackgroundEvent = {\n type: `${typeof controllerName}:cancelBackgroundEvent`;\n handler: CronjobController['cancelBackgroundEvent'];\n};\n\nexport type GetBackgroundEvents = {\n type: `${typeof controllerName}:getBackgroundEvents`;\n handler: CronjobController['getBackgroundEvents'];\n};\n\nexport type CronjobControllerActions =\n | GetAllSnaps\n | HandleSnapRequest\n | GetPermissions\n | CronjobControllerGetStateAction\n | ScheduleBackgroundEvent\n | CancelBackgroundEvent\n | GetBackgroundEvents;\n\nexport type CronjobControllerEvents =\n | SnapInstalled\n | SnapUninstalled\n | SnapUpdated\n | SnapEnabled\n | SnapDisabled\n | CronjobControllerStateChangeEvent;\n\nexport type CronjobControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n CronjobControllerActions,\n CronjobControllerEvents,\n CronjobControllerActions['type'],\n CronjobControllerEvents['type']\n>;\n\nexport const DAILY_TIMEOUT = inMilliseconds(24, Duration.Hour);\n\nexport type CronjobControllerArgs = {\n messenger: CronjobControllerMessenger;\n /**\n * Persisted state that will be used for rehydration.\n */\n state?: CronjobControllerState;\n};\n\nexport type Cronjob = {\n timer?: Timer;\n id: string;\n snapId: SnapId;\n} & CronjobSpecification;\n\nexport type StoredJobInformation = {\n lastRun: number;\n};\n\nexport type CronjobControllerState = {\n jobs: Record<string, StoredJobInformation>;\n events: Record<string, BackgroundEvent>;\n};\n\nconst controllerName = 'CronjobController';\n\n/**\n * Use this controller to register and schedule periodically executed jobs\n * using RPC method hooks.\n */\nexport class CronjobController extends BaseController<\n typeof controllerName,\n CronjobControllerState,\n CronjobControllerMessenger\n> {\n #dailyTimer!: Timer;\n\n #timers: Map<string, Timer>;\n\n // Mapping from jobId to snapId\n #snapIds: Map<string, SnapId>;\n\n constructor({ messenger, state }: CronjobControllerArgs) {\n super({\n messenger,\n metadata: {\n jobs: { persist: true, anonymous: false },\n events: { persist: true, anonymous: false },\n },\n name: controllerName,\n state: {\n jobs: {},\n events: {},\n ...state,\n },\n });\n this.#timers = new Map();\n this.#snapIds = new Map();\n\n this._handleSnapRegisterEvent = this._handleSnapRegisterEvent.bind(this);\n this._handleSnapUnregisterEvent =\n this._handleSnapUnregisterEvent.bind(this);\n this._handleEventSnapUpdated = this._handleEventSnapUpdated.bind(this);\n this._handleSnapDisabledEvent = this._handleSnapDisabledEvent.bind(this);\n this._handleSnapEnabledEvent = this._handleSnapEnabledEvent.bind(this);\n // Subscribe to Snap events\n /* eslint-disable @typescript-eslint/unbound-method */\n\n this.messagingSystem.subscribe(\n 'SnapController:snapInstalled',\n this._handleSnapRegisterEvent,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapUninstalled',\n this._handleSnapUnregisterEvent,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapEnabled',\n this._handleSnapEnabledEvent,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapDisabled',\n this._handleSnapDisabledEvent,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapUpdated',\n this._handleEventSnapUpdated,\n );\n /* eslint-enable @typescript-eslint/unbound-method */\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:scheduleBackgroundEvent`,\n (...args) => this.scheduleBackgroundEvent(...args),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:cancelBackgroundEvent`,\n (...args) => this.cancelBackgroundEvent(...args),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getBackgroundEvents`,\n (...args) => this.getBackgroundEvents(...args),\n );\n\n this.dailyCheckIn().catch((error) => {\n logError(error);\n });\n\n this.#rescheduleBackgroundEvents(Object.values(this.state.events));\n }\n\n /**\n * Retrieve all cronjob specifications for all runnable snaps.\n *\n * @returns Array of Cronjob specifications.\n */\n #getAllJobs(): Cronjob[] {\n const snaps = this.messagingSystem.call('SnapController:getAll');\n const filteredSnaps = getRunnableSnaps(snaps);\n\n const jobs = filteredSnaps.map((snap) => this.#getSnapJobs(snap.id));\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n return jobs.flat().filter((job) => job !== undefined) as Cronjob[];\n }\n\n /**\n * Retrieve all Cronjob specifications for a Snap.\n *\n * @param snapId - ID of a Snap.\n * @returns Array of Cronjob specifications.\n */\n #getSnapJobs(snapId: SnapId): Cronjob[] | undefined {\n const permissions = this.messagingSystem.call(\n 'PermissionController:getPermissions',\n snapId,\n );\n\n const permission = permissions?.[SnapEndowments.Cronjob];\n const definitions = getCronjobCaveatJobs(permission);\n\n return definitions?.map((definition, idx) => {\n return { ...definition, id: `${snapId}-${idx}`, snapId };\n });\n }\n\n /**\n * Register cron jobs for a given snap by getting specification from a permission caveats.\n * Once registered, each job will be scheduled.\n *\n * @param snapId - ID of a snap.\n */\n register(snapId: SnapId) {\n const jobs = this.#getSnapJobs(snapId);\n jobs?.forEach((job) => this.#schedule(job));\n }\n\n /**\n * Schedule a new job.\n * This will interpret the cron expression and tell the timer to execute the job\n * at the next suitable point in time.\n * Job last run state will be initialized afterwards.\n *\n * Note: Schedule will be skipped if the job's execution time is too far in the future and\n * will be revisited on a daily check.\n *\n * @param job - Cronjob specification.\n */\n #schedule(job: Cronjob) {\n if (this.#timers.has(job.id)) {\n return;\n }\n\n const parsed = parseCronExpression(job.expression);\n const next = parsed.next();\n const now = new Date();\n const ms = next.getTime() - now.getTime();\n\n // Don't schedule this job yet as it is too far in the future\n if (ms > DAILY_TIMEOUT) {\n return;\n }\n\n const timer = new Timer(ms);\n timer.start(() => {\n this.#executeCronjob(job).catch((error) => {\n // TODO: Decide how to handle errors.\n logError(error);\n });\n\n this.#timers.delete(job.id);\n this.#schedule(job);\n });\n\n if (!this.state.jobs[job.id]?.lastRun) {\n this.#updateJobLastRunState(job.id, 0); // 0 for init, never ran actually\n }\n\n this.#timers.set(job.id, timer);\n this.#snapIds.set(job.id, job.snapId);\n }\n\n /**\n * Execute job.\n *\n * @param job - Cronjob specification.\n */\n async #executeCronjob(job: Cronjob) {\n this.#updateJobLastRunState(job.id, Date.now());\n await this.messagingSystem.call('SnapController:handleRequest', {\n snapId: job.snapId,\n origin: '',\n handler: HandlerType.OnCronjob,\n request: job.request,\n });\n }\n\n /**\n * Schedule a background event.\n *\n * @param backgroundEventWithoutId - Background event.\n * @returns An id representing the background event.\n */\n scheduleBackgroundEvent(\n backgroundEventWithoutId: Omit<BackgroundEvent, 'id' | 'scheduledAt'>,\n ) {\n // Remove millisecond precision and convert to UTC.\n const scheduledAt = DateTime.fromJSDate(new Date())\n .toUTC()\n .startOf('second')\n .toISO({\n suppressMilliseconds: true,\n });\n\n assert(scheduledAt);\n\n const event = {\n ...backgroundEventWithoutId,\n id: nanoid(),\n scheduledAt,\n };\n\n this.#setUpBackgroundEvent(event);\n this.update((state) => {\n state.events[event.id] = castDraft(event);\n });\n\n return event.id;\n }\n\n /**\n * Cancel a background event.\n *\n * @param origin - The origin making the cancel call.\n * @param id - The id of the background event to cancel.\n * @throws If the event does not exist.\n */\n cancelBackgroundEvent(origin: string, id: string) {\n assert(\n this.state.events[id],\n `A background event with the id of \"${id}\" does not exist.`,\n );\n\n assert(\n this.state.events[id].snapId === origin,\n 'Only the origin that scheduled this event can cancel it.',\n );\n\n const timer = this.#timers.get(id);\n timer?.cancel();\n this.#timers.delete(id);\n this.#snapIds.delete(id);\n this.update((state) => {\n delete state.events[id];\n });\n }\n\n /**\n * A helper function to handle setup of the background event.\n *\n * @param event - A background event.\n */\n #setUpBackgroundEvent(event: BackgroundEvent) {\n const date = new Date(event.date);\n const now = new Date();\n const ms = date.getTime() - now.getTime();\n\n if (ms <= 0) {\n throw new Error('Cannot schedule an event in the past.');\n }\n\n const timer = new Timer(ms);\n timer.start(() => {\n this.messagingSystem\n .call('SnapController:handleRequest', {\n snapId: event.snapId,\n origin: '',\n handler: HandlerType.OnCronjob,\n request: event.request,\n })\n .catch((error) => {\n logError(error);\n });\n\n this.#timers.delete(event.id);\n this.#snapIds.delete(event.id);\n this.update((state) => {\n delete state.events[event.id];\n });\n });\n\n this.#timers.set(event.id, timer);\n this.#snapIds.set(event.id, event.snapId);\n }\n\n /**\n * Get a list of a Snap's background events.\n *\n * @param snapId - The id of the Snap to fetch background events for.\n * @returns An array of background events.\n */\n getBackgroundEvents(snapId: SnapId): BackgroundEvent[] {\n return Object.values(this.state.events).filter(\n (snapEvent) => snapEvent.snapId === snapId,\n );\n }\n\n /**\n * Unregister all jobs and background events related to the given snapId.\n *\n * @param snapId - ID of a snap.\n * @param skipEvents - Whether the unregistration process should skip scheduled background events.\n */\n unregister(snapId: SnapId, skipEvents = false) {\n const jobs = [...this.#snapIds.entries()].filter(\n ([_, jobSnapId]) => jobSnapId === snapId,\n );\n\n if (jobs.length) {\n const eventIds: string[] = [];\n jobs.forEach(([id]) => {\n const timer = this.#timers.get(id);\n if (timer) {\n timer.cancel();\n this.#timers.delete(id);\n this.#snapIds.delete(id);\n if (!skipEvents && this.state.events[id]) {\n eventIds.push(id);\n }\n }\n });\n\n if (eventIds.length > 0) {\n this.update((state) => {\n eventIds.forEach((id) => {\n delete state.events[id];\n });\n });\n }\n }\n }\n\n /**\n * Update time of a last run for the Cronjob specified by ID.\n *\n * @param jobId - ID of a cron job.\n * @param lastRun - Unix timestamp when the job was last ran.\n */\n #updateJobLastRunState(jobId: string, lastRun: number) {\n this.update((state) => {\n state.jobs[jobId] = {\n lastRun,\n };\n });\n }\n\n /**\n * Runs every 24 hours to check if new jobs need to be scheduled.\n *\n * This is necessary for longer running jobs that execute with more than 24 hours between them.\n */\n async dailyCheckIn() {\n const jobs = this.#getAllJobs();\n\n for (const job of jobs) {\n const parsed = parseCronExpression(job.expression);\n const lastRun = this.state.jobs[job.id]?.lastRun;\n // If a job was supposed to run while we were shut down but wasn't we run it now\n if (\n lastRun !== undefined &&\n parsed.hasPrev() &&\n parsed.prev().getTime() > lastRun\n ) {\n await this.#executeCronjob(job);\n }\n\n // Try scheduling, will fail if an existing scheduled job is found\n this.#schedule(job);\n }\n\n this.#dailyTimer = new Timer(DAILY_TIMEOUT);\n this.#dailyTimer.start(() => {\n this.dailyCheckIn().catch((error) => {\n // TODO: Decide how to handle errors.\n logError(error);\n });\n });\n }\n\n /**\n * Reschedule background events.\n *\n * @param backgroundEvents - A list of background events to reschdule.\n */\n #rescheduleBackgroundEvents(backgroundEvents: BackgroundEvent[]) {\n for (const snapEvent of backgroundEvents) {\n const { date } = snapEvent;\n const now = new Date();\n const then = new Date(date);\n if (then.getTime() < now.getTime()) {\n // Remove expired events from state\n this.update((state) => {\n delete state.events[snapEvent.id];\n });\n\n logWarning(\n `Background event with id \"${snapEvent.id}\" not scheduled as its date has expired.`,\n );\n } else {\n this.#setUpBackgroundEvent(snapEvent);\n }\n }\n }\n\n /**\n * Run controller teardown process and unsubscribe from Snap events.\n */\n destroy() {\n super.destroy();\n\n /* eslint-disable @typescript-eslint/unbound-method */\n this.messagingSystem.unsubscribe(\n 'SnapController:snapInstalled',\n this._handleSnapRegisterEvent,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapUninstalled',\n this._handleSnapUnregisterEvent,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapEnabled',\n this._handleSnapEnabledEvent,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapDisabled',\n this._handleSnapDisabledEvent,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapUpdated',\n this._handleEventSnapUpdated,\n );\n /* eslint-enable @typescript-eslint/unbound-method */\n\n this.#snapIds.forEach((snapId) => this.unregister(snapId));\n }\n\n /**\n * Handle events that should cause cronjobs to be registered.\n *\n * @param snap - Basic Snap information.\n */\n private _handleSnapRegisterEvent(snap: TruncatedSnap) {\n this.register(snap.id);\n }\n\n /**\n * Handle events that could cause cronjobs to be registered\n * and for background events to be rescheduled.\n *\n * @param snap - Basic Snap information.\n */\n private _handleSnapEnabledEvent(snap: TruncatedSnap) {\n const events = this.getBackgroundEvents(snap.id);\n this.#rescheduleBackgroundEvents(events);\n this.register(snap.id);\n }\n\n /**\n * Handle events that should cause cronjobs and background events to be unregistered.\n *\n * @param snap - Basic Snap information.\n */\n private _handleSnapUnregisterEvent(snap: TruncatedSnap) {\n this.unregister(snap.id);\n }\n\n /**\n * Handle events that should cause cronjobs and background events to be unregistered.\n *\n * @param snap - Basic Snap information.\n */\n private _handleSnapDisabledEvent(snap: TruncatedSnap) {\n this.unregister(snap.id, true);\n }\n\n /**\n * Handle cron jobs on 'snapUpdated' event.\n *\n * @param snap - Basic Snap information.\n */\n private _handleEventSnapUpdated(snap: TruncatedSnap) {\n this.unregister(snap.id);\n this.register(snap.id);\n }\n}\n"]}