@metamask/snaps-controllers 12.3.1 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/dist/cronjob/CronjobController.cjs +250 -276
  3. package/dist/cronjob/CronjobController.cjs.map +1 -1
  4. package/dist/cronjob/CronjobController.d.cts +61 -78
  5. package/dist/cronjob/CronjobController.d.cts.map +1 -1
  6. package/dist/cronjob/CronjobController.d.mts +61 -78
  7. package/dist/cronjob/CronjobController.d.mts.map +1 -1
  8. package/dist/cronjob/CronjobController.mjs +251 -277
  9. package/dist/cronjob/CronjobController.mjs.map +1 -1
  10. package/dist/cronjob/utils.cjs +79 -0
  11. package/dist/cronjob/utils.cjs.map +1 -0
  12. package/dist/cronjob/utils.d.cts +25 -0
  13. package/dist/cronjob/utils.d.cts.map +1 -0
  14. package/dist/cronjob/utils.d.mts +25 -0
  15. package/dist/cronjob/utils.d.mts.map +1 -0
  16. package/dist/cronjob/utils.mjs +75 -0
  17. package/dist/cronjob/utils.mjs.map +1 -0
  18. package/dist/insights/SnapInsightsController.cjs +199 -149
  19. package/dist/insights/SnapInsightsController.cjs.map +1 -1
  20. package/dist/insights/SnapInsightsController.mjs +198 -148
  21. package/dist/insights/SnapInsightsController.mjs.map +1 -1
  22. package/dist/interface/SnapInterfaceController.cjs +160 -101
  23. package/dist/interface/SnapInterfaceController.cjs.map +1 -1
  24. package/dist/interface/SnapInterfaceController.mjs +160 -101
  25. package/dist/interface/SnapInterfaceController.mjs.map +1 -1
  26. package/dist/multichain/MultichainRouter.cjs +117 -114
  27. package/dist/multichain/MultichainRouter.cjs.map +1 -1
  28. package/dist/multichain/MultichainRouter.mjs +117 -114
  29. package/dist/multichain/MultichainRouter.mjs.map +1 -1
  30. package/dist/services/AbstractExecutionService.cjs +131 -139
  31. package/dist/services/AbstractExecutionService.cjs.map +1 -1
  32. package/dist/services/AbstractExecutionService.mjs +131 -139
  33. package/dist/services/AbstractExecutionService.mjs.map +1 -1
  34. package/dist/services/ProxyPostMessageStream.cjs +19 -26
  35. package/dist/services/ProxyPostMessageStream.cjs.map +1 -1
  36. package/dist/services/ProxyPostMessageStream.mjs +19 -26
  37. package/dist/services/ProxyPostMessageStream.mjs.map +1 -1
  38. package/dist/services/iframe/IframeExecutionService.cjs +1 -0
  39. package/dist/services/iframe/IframeExecutionService.cjs.map +1 -1
  40. package/dist/services/iframe/IframeExecutionService.mjs +1 -0
  41. package/dist/services/iframe/IframeExecutionService.mjs.map +1 -1
  42. package/dist/services/offscreen/OffscreenExecutionService.cjs +3 -16
  43. package/dist/services/offscreen/OffscreenExecutionService.cjs.map +1 -1
  44. package/dist/services/offscreen/OffscreenExecutionService.mjs +3 -16
  45. package/dist/services/offscreen/OffscreenExecutionService.mjs.map +1 -1
  46. package/dist/services/proxy/ProxyExecutionService.cjs +4 -17
  47. package/dist/services/proxy/ProxyExecutionService.cjs.map +1 -1
  48. package/dist/services/proxy/ProxyExecutionService.mjs +4 -17
  49. package/dist/services/proxy/ProxyExecutionService.mjs.map +1 -1
  50. package/dist/services/webview/WebViewExecutionService.cjs +6 -19
  51. package/dist/services/webview/WebViewExecutionService.cjs.map +1 -1
  52. package/dist/services/webview/WebViewExecutionService.mjs +6 -19
  53. package/dist/services/webview/WebViewExecutionService.mjs.map +1 -1
  54. package/dist/services/webview/WebViewMessageStream.cjs +13 -26
  55. package/dist/services/webview/WebViewMessageStream.cjs.map +1 -1
  56. package/dist/services/webview/WebViewMessageStream.mjs +13 -26
  57. package/dist/services/webview/WebViewMessageStream.mjs.map +1 -1
  58. package/dist/snaps/SnapController.cjs +1370 -1161
  59. package/dist/snaps/SnapController.cjs.map +1 -1
  60. package/dist/snaps/SnapController.d.cts +4 -4
  61. package/dist/snaps/SnapController.d.cts.map +1 -1
  62. package/dist/snaps/SnapController.d.mts +4 -4
  63. package/dist/snaps/SnapController.d.mts.map +1 -1
  64. package/dist/snaps/SnapController.mjs +1370 -1161
  65. package/dist/snaps/SnapController.mjs.map +1 -1
  66. package/dist/snaps/Timer.cjs +4 -0
  67. package/dist/snaps/Timer.cjs.map +1 -1
  68. package/dist/snaps/Timer.mjs +4 -0
  69. package/dist/snaps/Timer.mjs.map +1 -1
  70. package/dist/snaps/location/http.cjs +20 -4
  71. package/dist/snaps/location/http.cjs.map +1 -1
  72. package/dist/snaps/location/http.mjs +20 -4
  73. package/dist/snaps/location/http.mjs.map +1 -1
  74. package/dist/snaps/location/local.cjs +4 -17
  75. package/dist/snaps/location/local.cjs.map +1 -1
  76. package/dist/snaps/location/local.mjs +4 -17
  77. package/dist/snaps/location/local.mjs.map +1 -1
  78. package/dist/snaps/location/npm.cjs +28 -48
  79. package/dist/snaps/location/npm.cjs.map +1 -1
  80. package/dist/snaps/location/npm.d.cts.map +1 -1
  81. package/dist/snaps/location/npm.d.mts.map +1 -1
  82. package/dist/snaps/location/npm.mjs +28 -48
  83. package/dist/snaps/location/npm.mjs.map +1 -1
  84. package/dist/snaps/registry/json.cjs +173 -166
  85. package/dist/snaps/registry/json.cjs.map +1 -1
  86. package/dist/snaps/registry/json.mjs +172 -165
  87. package/dist/snaps/registry/json.mjs.map +1 -1
  88. package/package.json +9 -9
@@ -1,16 +1,4 @@
1
1
  "use strict";
2
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
- if (kind === "m") throw new TypeError("Private method is not writable");
4
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
- };
8
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
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
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
- };
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
15
3
  exports.CronjobController = exports.DAILY_TIMEOUT = void 0;
16
4
  const base_controller_1 = require("@metamask/base-controller");
@@ -20,106 +8,67 @@ const utils_1 = require("@metamask/utils");
20
8
  const immer_1 = require("immer");
21
9
  const luxon_1 = require("luxon");
22
10
  const nanoid_1 = require("nanoid");
23
- const __1 = require("../index.cjs");
11
+ const utils_2 = require("./utils.cjs");
12
+ const constants_1 = require("../snaps/constants.cjs");
24
13
  const Timer_1 = require("../snaps/Timer.cjs");
25
14
  exports.DAILY_TIMEOUT = (0, utils_1.inMilliseconds)(24, utils_1.Duration.Hour);
26
15
  const controllerName = 'CronjobController';
27
16
  /**
28
- * Use this controller to register and schedule periodically executed jobs
29
- * using RPC method hooks.
17
+ * The cronjob controller is responsible for managing cronjobs and background
18
+ * events for Snaps. It allows Snaps to schedule events that will be executed
19
+ * at a later time.
30
20
  */
31
21
  class CronjobController extends base_controller_1.BaseController {
22
+ #timers;
23
+ #dailyTimer = new Timer_1.Timer(exports.DAILY_TIMEOUT);
32
24
  constructor({ messenger, state }) {
33
25
  super({
34
26
  messenger,
35
27
  metadata: {
36
- jobs: { persist: true, anonymous: false },
37
28
  events: { persist: true, anonymous: false },
38
29
  },
39
30
  name: controllerName,
40
31
  state: {
41
- jobs: {},
42
32
  events: {},
43
33
  ...state,
44
34
  },
45
35
  });
46
- _CronjobController_instances.add(this);
47
- _CronjobController_dailyTimer.set(this, void 0);
48
- _CronjobController_timers.set(this, void 0);
49
- // Mapping from jobId to snapId
50
- _CronjobController_snapIds.set(this, void 0);
51
- __classPrivateFieldSet(this, _CronjobController_timers, new Map(), "f");
52
- __classPrivateFieldSet(this, _CronjobController_snapIds, new Map(), "f");
53
- this._handleSnapRegisterEvent = this._handleSnapRegisterEvent.bind(this);
54
- this._handleSnapUnregisterEvent =
55
- this._handleSnapUnregisterEvent.bind(this);
56
- this._handleEventSnapUpdated = this._handleEventSnapUpdated.bind(this);
57
- this._handleSnapDisabledEvent = this._handleSnapDisabledEvent.bind(this);
58
- this._handleSnapEnabledEvent = this._handleSnapEnabledEvent.bind(this);
59
- // Subscribe to Snap events
60
- /* eslint-disable @typescript-eslint/unbound-method */
61
- this.messagingSystem.subscribe('SnapController:snapInstalled', this._handleSnapRegisterEvent);
62
- this.messagingSystem.subscribe('SnapController:snapUninstalled', this._handleSnapUnregisterEvent);
63
- this.messagingSystem.subscribe('SnapController:snapEnabled', this._handleSnapEnabledEvent);
64
- this.messagingSystem.subscribe('SnapController:snapDisabled', this._handleSnapDisabledEvent);
65
- this.messagingSystem.subscribe('SnapController:snapUpdated', this._handleEventSnapUpdated);
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));
70
- this.dailyCheckIn();
71
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_rescheduleBackgroundEvents).call(this, Object.values(this.state.events));
36
+ this.#timers = new Map();
37
+ this.messagingSystem.subscribe('SnapController:snapInstalled', this.#handleSnapInstalledEvent);
38
+ this.messagingSystem.subscribe('SnapController:snapUninstalled', this.#handleSnapUninstalledEvent);
39
+ this.messagingSystem.subscribe('SnapController:snapEnabled', this.#handleSnapEnabledEvent);
40
+ this.messagingSystem.subscribe('SnapController:snapDisabled', this.#handleSnapDisabledEvent);
41
+ this.messagingSystem.subscribe('SnapController:snapUpdated', this.#handleSnapUpdatedEvent);
42
+ this.messagingSystem.registerActionHandler(`${controllerName}:schedule`, (...args) => this.schedule(...args));
43
+ this.messagingSystem.registerActionHandler(`${controllerName}:cancel`, (...args) => this.cancel(...args));
44
+ this.messagingSystem.registerActionHandler(`${controllerName}:get`, (...args) => this.get(...args));
45
+ this.#start();
46
+ this.#clear();
47
+ this.#reschedule();
72
48
  }
73
49
  /**
74
- * Register cron jobs for a given snap by getting specification from a permission caveats.
75
- * Once registered, each job will be scheduled.
50
+ * Schedule a non-recurring background event.
76
51
  *
77
- * @param snapId - ID of a snap.
78
- */
79
- register(snapId) {
80
- const jobs = __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_getSnapJobs).call(this, snapId);
81
- jobs?.forEach((job) => __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_schedule).call(this, job));
82
- }
83
- /**
84
- * Schedule a background event.
85
- *
86
- * @param backgroundEventWithoutId - Background event.
87
- * @returns An id representing the background event.
52
+ * @param event - The event to schedule.
53
+ * @returns The ID of the scheduled event.
88
54
  */
89
- scheduleBackgroundEvent(backgroundEventWithoutId) {
90
- // Remove millisecond precision and convert to UTC.
91
- const scheduledAt = luxon_1.DateTime.fromJSDate(new Date())
92
- .toUTC()
93
- .startOf('second')
94
- .toISO({
95
- suppressMilliseconds: true,
55
+ schedule(event) {
56
+ return this.#add({
57
+ ...event,
58
+ recurring: false,
96
59
  });
97
- (0, utils_1.assert)(scheduledAt);
98
- const event = {
99
- ...backgroundEventWithoutId,
100
- id: (0, nanoid_1.nanoid)(),
101
- scheduledAt,
102
- };
103
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_setUpBackgroundEvent).call(this, event);
104
- return event.id;
105
60
  }
106
61
  /**
107
- * Cancel a background event.
62
+ * Cancel an event.
108
63
  *
109
64
  * @param origin - The origin making the cancel call.
110
- * @param id - The id of the background event to cancel.
65
+ * @param id - The id of the event to cancel.
111
66
  * @throws If the event does not exist.
112
67
  */
113
- cancelBackgroundEvent(origin, id) {
68
+ cancel(origin, id) {
114
69
  (0, utils_1.assert)(this.state.events[id], `A background event with the id of "${id}" does not exist.`);
115
70
  (0, utils_1.assert)(this.state.events[id].snapId === origin, 'Only the origin that scheduled this event can cancel it.');
116
- const timer = __classPrivateFieldGet(this, _CronjobController_timers, "f").get(id);
117
- timer?.cancel();
118
- __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(id);
119
- __classPrivateFieldGet(this, _CronjobController_snapIds, "f").delete(id);
120
- this.update((state) => {
121
- delete state.events[id];
122
- });
71
+ this.#cancel(id);
123
72
  }
124
73
  /**
125
74
  * Get a list of a Snap's background events.
@@ -127,252 +76,277 @@ class CronjobController extends base_controller_1.BaseController {
127
76
  * @param snapId - The id of the Snap to fetch background events for.
128
77
  * @returns An array of background events.
129
78
  */
130
- getBackgroundEvents(snapId) {
79
+ get(snapId) {
131
80
  return Object.values(this.state.events)
132
- .filter((snapEvent) => snapEvent.snapId === snapId)
81
+ .filter((snapEvent) => snapEvent.snapId === snapId && !snapEvent.recurring)
133
82
  .map((event) => ({
134
83
  ...event,
135
- // Truncate dates to remove milliseconds.
136
84
  date: (0, snaps_utils_1.toCensoredISO8601String)(event.date),
85
+ scheduledAt: (0, snaps_utils_1.toCensoredISO8601String)(event.scheduledAt),
137
86
  }));
138
87
  }
139
88
  /**
140
- * Unregister all jobs and background events related to the given snapId.
89
+ * Register cronjobs for a given Snap by getting specification from the
90
+ * permission caveats. Once registered, each job will be scheduled.
141
91
  *
142
- * @param snapId - ID of a snap.
143
- * @param skipEvents - Whether the unregistration process should skip scheduled background events.
92
+ * @param snapId - The snap ID to register jobs for.
144
93
  */
145
- unregister(snapId, skipEvents = false) {
146
- const jobs = [...__classPrivateFieldGet(this, _CronjobController_snapIds, "f").entries()].filter(([_, jobSnapId]) => jobSnapId === snapId);
147
- if (jobs.length) {
148
- const eventIds = [];
149
- jobs.forEach(([id]) => {
150
- const timer = __classPrivateFieldGet(this, _CronjobController_timers, "f").get(id);
151
- if (timer) {
152
- timer.cancel();
153
- __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(id);
154
- __classPrivateFieldGet(this, _CronjobController_snapIds, "f").delete(id);
155
- if (!skipEvents && this.state.events[id]) {
156
- eventIds.push(id);
157
- }
158
- }
159
- });
160
- if (eventIds.length > 0) {
161
- this.update((state) => {
162
- eventIds.forEach((id) => {
163
- delete state.events[id];
164
- });
165
- });
166
- }
167
- }
94
+ register(snapId) {
95
+ const jobs = this.#getSnapCronjobs(snapId);
96
+ jobs?.forEach((job) => this.#add(job));
168
97
  }
169
98
  /**
170
- * Runs every 24 hours to check if new jobs need to be scheduled.
99
+ * Unregister all cronjobs and background events for a given Snap.
171
100
  *
172
- * This is necessary for longer running jobs that execute with more than 24 hours between them.
101
+ * @param snapId - ID of a snap.
173
102
  */
174
- dailyCheckIn() {
175
- const jobs = __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_getAllJobs).call(this);
176
- for (const job of jobs) {
177
- const parsed = (0, snaps_utils_1.parseCronExpression)(job.expression);
178
- const lastRun = this.state.jobs[job.id]?.lastRun;
179
- // If a job was supposed to run while we were shut down but wasn't we run it now
180
- if (lastRun !== undefined &&
181
- parsed.hasPrev() &&
182
- parsed.prev().getTime() > lastRun) {
183
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_executeCronjob).call(this, job).catch((error) => {
184
- (0, snaps_utils_1.logError)(error);
185
- });
103
+ unregister(snapId) {
104
+ for (const [id, event] of Object.entries(this.state.events)) {
105
+ if (event.snapId === snapId) {
106
+ this.#cancel(id);
186
107
  }
187
- // Try scheduling, will fail if an existing scheduled job is found
188
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_schedule).call(this, job);
189
108
  }
190
- __classPrivateFieldSet(this, _CronjobController_dailyTimer, new Timer_1.Timer(exports.DAILY_TIMEOUT), "f");
191
- __classPrivateFieldGet(this, _CronjobController_dailyTimer, "f").start(() => {
192
- this.dailyCheckIn();
193
- });
194
109
  }
195
110
  /**
196
111
  * Run controller teardown process and unsubscribe from Snap events.
197
112
  */
198
113
  destroy() {
199
114
  super.destroy();
200
- /* eslint-disable @typescript-eslint/unbound-method */
201
- this.messagingSystem.unsubscribe('SnapController:snapInstalled', this._handleSnapRegisterEvent);
202
- this.messagingSystem.unsubscribe('SnapController:snapUninstalled', this._handleSnapUnregisterEvent);
203
- this.messagingSystem.unsubscribe('SnapController:snapEnabled', this._handleSnapEnabledEvent);
204
- this.messagingSystem.unsubscribe('SnapController:snapDisabled', this._handleSnapDisabledEvent);
205
- this.messagingSystem.unsubscribe('SnapController:snapUpdated', this._handleEventSnapUpdated);
206
- /* eslint-enable @typescript-eslint/unbound-method */
207
- __classPrivateFieldGet(this, _CronjobController_snapIds, "f").forEach((snapId) => this.unregister(snapId));
115
+ this.messagingSystem.unsubscribe('SnapController:snapInstalled', this.#handleSnapInstalledEvent);
116
+ this.messagingSystem.unsubscribe('SnapController:snapUninstalled', this.#handleSnapUninstalledEvent);
117
+ this.messagingSystem.unsubscribe('SnapController:snapEnabled', this.#handleSnapEnabledEvent);
118
+ this.messagingSystem.unsubscribe('SnapController:snapDisabled', this.#handleSnapDisabledEvent);
119
+ this.messagingSystem.unsubscribe('SnapController:snapUpdated', this.#handleSnapUpdatedEvent);
120
+ // Cancel all timers and clear the map.
121
+ this.#timers.forEach((timer) => timer.cancel());
122
+ this.#timers.clear();
123
+ if (this.#dailyTimer.status === 'running') {
124
+ this.#dailyTimer.cancel();
125
+ }
208
126
  }
209
127
  /**
210
- * Handle events that should cause cronjobs to be registered.
211
- *
212
- * @param snap - Basic Snap information.
128
+ * Start the daily timer that will reschedule events every 24 hours.
213
129
  */
214
- // TODO: Either fix this lint violation or explain why it's necessary to
215
- // ignore.
216
- // eslint-disable-next-line no-restricted-syntax
217
- _handleSnapRegisterEvent(snap) {
218
- this.register(snap.id);
130
+ #start() {
131
+ this.#dailyTimer = new Timer_1.Timer(exports.DAILY_TIMEOUT);
132
+ this.#dailyTimer.start(() => {
133
+ this.#reschedule();
134
+ this.#start();
135
+ });
219
136
  }
220
137
  /**
221
- * Handle events that could cause cronjobs to be registered
222
- * and for background events to be rescheduled.
138
+ * Add a cronjob or background event to the controller state and schedule it
139
+ * for execution.
223
140
  *
224
- * @param snap - Basic Snap information.
141
+ * @param event - The event to schedule.
142
+ * @returns The ID of the scheduled event.
225
143
  */
226
- // TODO: Either fix this lint violation or explain why it's necessary to
227
- // ignore.
228
- // eslint-disable-next-line no-restricted-syntax
229
- _handleSnapEnabledEvent(snap) {
230
- const events = this.getBackgroundEvents(snap.id);
231
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_rescheduleBackgroundEvents).call(this, events);
232
- this.register(snap.id);
144
+ #add(event) {
145
+ const id = event.id ?? (0, nanoid_1.nanoid)();
146
+ const internalEvent = {
147
+ ...event,
148
+ id,
149
+ date: (0, utils_2.getExecutionDate)(event.schedule),
150
+ scheduledAt: new Date().toISOString(),
151
+ };
152
+ this.update((state) => {
153
+ state.events[internalEvent.id] = (0, immer_1.castDraft)(internalEvent);
154
+ });
155
+ this.#schedule(internalEvent);
156
+ return id;
233
157
  }
234
158
  /**
235
- * Handle events that should cause cronjobs and background events to be unregistered.
159
+ * Get the next execution date for a given event and start a timer for it.
236
160
  *
237
- * @param snap - Basic Snap information.
161
+ * @param event - The event to schedule.
238
162
  */
239
- // TODO: Either fix this lint violation or explain why it's necessary to
240
- // ignore.
241
- // eslint-disable-next-line no-restricted-syntax
242
- _handleSnapUnregisterEvent(snap) {
243
- this.unregister(snap.id);
163
+ #schedule(event) {
164
+ const date = (0, utils_2.getExecutionDate)(event.schedule);
165
+ this.update((state) => {
166
+ state.events[event.id].date = date;
167
+ });
168
+ this.#startTimer({
169
+ ...event,
170
+ date,
171
+ });
244
172
  }
245
173
  /**
246
- * Handle events that should cause cronjobs and background events to be unregistered.
174
+ * Set up and start a timer for the given event.
247
175
  *
248
- * @param snap - Basic Snap information.
176
+ * @param event - The event to schedule.
177
+ * @throws If the event is scheduled in the past.
249
178
  */
250
- // TODO: Either fix this lint violation or explain why it's necessary to
251
- // ignore.
252
- // eslint-disable-next-line no-restricted-syntax
253
- _handleSnapDisabledEvent(snap) {
254
- this.unregister(snap.id, true);
179
+ #startTimer(event) {
180
+ const ms = luxon_1.DateTime.fromISO(event.date, { setZone: true }).toMillis() - Date.now();
181
+ // We don't schedule this job yet as it is too far in the future.
182
+ if (ms > exports.DAILY_TIMEOUT) {
183
+ return;
184
+ }
185
+ const timer = new Timer_1.Timer(ms);
186
+ timer.start(() => {
187
+ this.#execute(event);
188
+ });
189
+ this.#timers.set(event.id, timer);
255
190
  }
256
191
  /**
257
- * Handle cron jobs on 'snapUpdated' event.
192
+ * Execute a background event. This method is called when the event's timer
193
+ * expires.
258
194
  *
259
- * @param snap - Basic Snap information.
195
+ * If the event is not recurring, it will be removed from the state after
196
+ * execution. If it is recurring, it will be rescheduled.
197
+ *
198
+ * @param event - The event to execute.
260
199
  */
261
- // TODO: Either fix this lint violation or explain why it's necessary to
262
- // ignore.
263
- // eslint-disable-next-line no-restricted-syntax
264
- _handleEventSnapUpdated(snap) {
265
- this.unregister(snap.id);
266
- this.register(snap.id);
267
- }
268
- }
269
- exports.CronjobController = CronjobController;
270
- _CronjobController_dailyTimer = new WeakMap(), _CronjobController_timers = new WeakMap(), _CronjobController_snapIds = new WeakMap(), _CronjobController_instances = new WeakSet(), _CronjobController_getAllJobs = function _CronjobController_getAllJobs() {
271
- const snaps = this.messagingSystem.call('SnapController:getAll');
272
- const filteredSnaps = (0, __1.getRunnableSnaps)(snaps);
273
- const jobs = filteredSnaps.map((snap) => __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_getSnapJobs).call(this, snap.id));
274
- return jobs.flat().filter((job) => job !== undefined);
275
- }, _CronjobController_getSnapJobs = function _CronjobController_getSnapJobs(snapId) {
276
- const permissions = this.messagingSystem.call('PermissionController:getPermissions', snapId);
277
- const permission = permissions?.[snaps_rpc_methods_1.SnapEndowments.Cronjob];
278
- const definitions = (0, snaps_rpc_methods_1.getCronjobCaveatJobs)(permission);
279
- return definitions?.map((definition, idx) => {
280
- return { ...definition, id: `${snapId}-${idx}`, snapId };
281
- });
282
- }, _CronjobController_schedule = function _CronjobController_schedule(job) {
283
- if (__classPrivateFieldGet(this, _CronjobController_timers, "f").has(job.id)) {
284
- return;
285
- }
286
- const parsed = (0, snaps_utils_1.parseCronExpression)(job.expression);
287
- const next = parsed.next();
288
- const now = new Date();
289
- const ms = next.getTime() - now.getTime();
290
- // Don't schedule this job yet as it is too far in the future
291
- if (ms > exports.DAILY_TIMEOUT) {
292
- return;
293
- }
294
- const timer = new Timer_1.Timer(ms);
295
- timer.start(() => {
296
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_executeCronjob).call(this, job).catch((error) => {
297
- // TODO: Decide how to handle errors.
298
- (0, snaps_utils_1.logError)(error);
299
- });
300
- __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(job.id);
301
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_schedule).call(this, job);
302
- });
303
- if (!this.state.jobs[job.id]?.lastRun) {
304
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_updateJobLastRunState).call(this, job.id, 0); // 0 for init, never ran actually
305
- }
306
- __classPrivateFieldGet(this, _CronjobController_timers, "f").set(job.id, timer);
307
- __classPrivateFieldGet(this, _CronjobController_snapIds, "f").set(job.id, job.snapId);
308
- }, _CronjobController_executeCronjob =
309
- /**
310
- * Execute job.
311
- *
312
- * @param job - Cronjob specification.
313
- */
314
- async function _CronjobController_executeCronjob(job) {
315
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_updateJobLastRunState).call(this, job.id, Date.now());
316
- await this.messagingSystem.call('SnapController:handleRequest', {
317
- snapId: job.snapId,
318
- origin: 'metamask',
319
- handler: snaps_utils_1.HandlerType.OnCronjob,
320
- request: job.request,
321
- });
322
- }, _CronjobController_setUpBackgroundEvent = function _CronjobController_setUpBackgroundEvent(event) {
323
- const date = new Date(event.date);
324
- const now = new Date();
325
- const ms = date.getTime() - now.getTime();
326
- if (ms <= 0) {
327
- throw new Error('Cannot schedule an event in the past.');
328
- }
329
- // The event may already be in state when we get here.
330
- if (!(0, utils_1.hasProperty)(this.state.events, event.id)) {
331
- this.update((state) => {
332
- state.events[event.id] = (0, immer_1.castDraft)(event);
333
- });
334
- }
335
- const timer = new Timer_1.Timer(ms);
336
- timer.start(() => {
200
+ #execute(event) {
337
201
  this.messagingSystem
338
202
  .call('SnapController:handleRequest', {
339
203
  snapId: event.snapId,
340
- origin: 'metamask',
204
+ origin: constants_1.METAMASK_ORIGIN,
341
205
  handler: snaps_utils_1.HandlerType.OnCronjob,
342
206
  request: event.request,
343
207
  })
344
208
  .catch((error) => {
345
- (0, snaps_utils_1.logError)(error);
209
+ (0, snaps_utils_1.logError)(`An error occurred while executing an event for Snap "${event.snapId}":`, error);
346
210
  });
347
- __classPrivateFieldGet(this, _CronjobController_timers, "f").delete(event.id);
348
- __classPrivateFieldGet(this, _CronjobController_snapIds, "f").delete(event.id);
349
- this.update((state) => {
350
- delete state.events[event.id];
351
- });
352
- });
353
- __classPrivateFieldGet(this, _CronjobController_timers, "f").set(event.id, timer);
354
- __classPrivateFieldGet(this, _CronjobController_snapIds, "f").set(event.id, event.snapId);
355
- }, _CronjobController_updateJobLastRunState = function _CronjobController_updateJobLastRunState(jobId, lastRun) {
356
- this.update((state) => {
357
- state.jobs[jobId] = {
358
- lastRun,
359
- };
360
- });
361
- }, _CronjobController_rescheduleBackgroundEvents = function _CronjobController_rescheduleBackgroundEvents(backgroundEvents) {
362
- for (const snapEvent of backgroundEvents) {
363
- const { date } = snapEvent;
364
- const now = new Date();
365
- const then = new Date(date);
366
- if (then.getTime() < now.getTime()) {
367
- // Remove expired events from state
211
+ this.#timers.delete(event.id);
212
+ // Non-recurring events are removed from the state after execution, and
213
+ // recurring events are rescheduled.
214
+ if (!event.recurring) {
368
215
  this.update((state) => {
369
- delete state.events[snapEvent.id];
216
+ delete state.events[event.id];
370
217
  });
371
- (0, snaps_utils_1.logWarning)(`Background event with id "${snapEvent.id}" not scheduled as its date has expired.`);
218
+ return;
372
219
  }
373
- else {
374
- __classPrivateFieldGet(this, _CronjobController_instances, "m", _CronjobController_setUpBackgroundEvent).call(this, snapEvent);
220
+ this.#schedule(event);
221
+ }
222
+ /**
223
+ * Cancel a background event by its ID. Unlike {@link cancel}, this method
224
+ * does not check the origin of the event, so it can be used internally.
225
+ *
226
+ * @param id - The ID of the background event to cancel.
227
+ */
228
+ #cancel(id) {
229
+ const timer = this.#timers.get(id);
230
+ timer?.cancel();
231
+ this.#timers.delete(id);
232
+ this.update((state) => {
233
+ delete state.events[id];
234
+ });
235
+ }
236
+ /**
237
+ * Retrieve all cronjob specifications for a Snap.
238
+ *
239
+ * @param snapId - ID of a Snap.
240
+ * @returns Array of cronjob specifications.
241
+ */
242
+ #getSnapCronjobs(snapId) {
243
+ const permissions = this.messagingSystem.call('PermissionController:getPermissions', snapId);
244
+ const permission = permissions?.[snaps_rpc_methods_1.SnapEndowments.Cronjob];
245
+ const definitions = (0, snaps_rpc_methods_1.getCronjobCaveatJobs)(permission);
246
+ if (!definitions) {
247
+ return [];
375
248
  }
249
+ return definitions.map((definition, idx) => {
250
+ return {
251
+ snapId,
252
+ id: `cronjob-${snapId}-${idx}`,
253
+ request: definition.request,
254
+ schedule: (0, utils_2.getCronjobSpecificationSchedule)(definition),
255
+ recurring: true,
256
+ };
257
+ });
376
258
  }
377
- };
259
+ /**
260
+ * Handle events that should cause cron jobs to be registered.
261
+ *
262
+ * @param snap - Basic Snap information.
263
+ */
264
+ #handleSnapInstalledEvent = (snap) => {
265
+ this.register(snap.id);
266
+ };
267
+ /**
268
+ * Handle the Snap enabled event. This checks if the Snap has any cronjobs or
269
+ * background events that need to be rescheduled.
270
+ *
271
+ * @param snap - Basic Snap information.
272
+ */
273
+ #handleSnapEnabledEvent = (snap) => {
274
+ const events = this.get(snap.id);
275
+ this.#reschedule(events);
276
+ this.register(snap.id);
277
+ };
278
+ /**
279
+ * Handle events that should cause cronjobs and background events to be
280
+ * unregistered.
281
+ *
282
+ * @param snap - Basic Snap information.
283
+ */
284
+ #handleSnapUninstalledEvent = (snap) => {
285
+ this.unregister(snap.id);
286
+ };
287
+ /**
288
+ * Handle events that should cause cronjobs and background events to be
289
+ * unregistered.
290
+ *
291
+ * @param snap - Basic Snap information.
292
+ */
293
+ #handleSnapDisabledEvent = (snap) => {
294
+ this.unregister(snap.id);
295
+ };
296
+ /**
297
+ * Handle cron jobs on 'snapUpdated' event.
298
+ *
299
+ * @param snap - Basic Snap information.
300
+ */
301
+ #handleSnapUpdatedEvent = (snap) => {
302
+ this.unregister(snap.id);
303
+ this.register(snap.id);
304
+ };
305
+ /**
306
+ * Reschedule events that are yet to be executed. This should be called on
307
+ * controller initialization and once every 24 hours to ensure that
308
+ * background events are scheduled correctly.
309
+ *
310
+ * @param events - An array of events to reschedule. Defaults to all events in
311
+ * the controller state.
312
+ */
313
+ #reschedule(events = Object.values(this.state.events)) {
314
+ const now = Date.now();
315
+ for (const event of events) {
316
+ if (this.#timers.has(event.id)) {
317
+ // If the timer for this event already exists, we don't need to
318
+ // reschedule it.
319
+ continue;
320
+ }
321
+ const eventDate = luxon_1.DateTime.fromISO(event.date, {
322
+ setZone: true,
323
+ })
324
+ .toUTC()
325
+ .toMillis();
326
+ // If the event is recurring and the date is in the past, execute it
327
+ // immediately.
328
+ if (event.recurring && eventDate <= now) {
329
+ this.#execute(event);
330
+ }
331
+ this.#schedule(event);
332
+ }
333
+ }
334
+ /**
335
+ * Clear non-recurring events that are past their scheduled time.
336
+ */
337
+ #clear() {
338
+ const now = Date.now();
339
+ for (const event of Object.values(this.state.events)) {
340
+ const eventDate = luxon_1.DateTime.fromISO(event.date, {
341
+ setZone: true,
342
+ })
343
+ .toUTC()
344
+ .toMillis();
345
+ if (!event.recurring && eventDate < now) {
346
+ this.#cancel(event.id);
347
+ }
348
+ }
349
+ }
350
+ }
351
+ exports.CronjobController = CronjobController;
378
352
  //# sourceMappingURL=CronjobController.cjs.map