@metamask/snaps-controllers 9.16.0 → 9.18.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 +24 -1
- package/dist/cronjob/CronjobController.cjs +203 -100
- package/dist/cronjob/CronjobController.cjs.map +1 -1
- package/dist/cronjob/CronjobController.d.cts +46 -35
- package/dist/cronjob/CronjobController.d.cts.map +1 -1
- package/dist/cronjob/CronjobController.d.mts +46 -35
- package/dist/cronjob/CronjobController.d.mts.map +1 -1
- package/dist/cronjob/CronjobController.mjs +205 -102
- package/dist/cronjob/CronjobController.mjs.map +1 -1
- package/dist/snaps/SnapController.cjs +136 -19
- package/dist/snaps/SnapController.cjs.map +1 -1
- package/dist/snaps/SnapController.d.cts +18 -1
- package/dist/snaps/SnapController.d.cts.map +1 -1
- package/dist/snaps/SnapController.d.mts +18 -1
- package/dist/snaps/SnapController.d.mts.map +1 -1
- package/dist/snaps/SnapController.mjs +138 -21
- package/dist/snaps/SnapController.mjs.map +1 -1
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.mts +1 -1
- package/package.json +21 -18
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [9.18.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Add support for `onAssetsLookup` and `onAssetsConversion` handlers ([#3028](https://github.com/MetaMask/snaps/pull/3028))
|
|
15
|
+
|
|
16
|
+
## [9.17.0]
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Added support for non-recurring cronjobs via `snap_scheduleBackgroundEvent` ([#2941](https://github.com/MetaMask/snaps/pull/2941))
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- 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))
|
|
25
|
+
- Cache snap state in memory for improved performance ([#2980](https://github.com/MetaMask/snaps/pull/2980))
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Stop storing messenger manually in `CronjobController` ([#3006](https://github.com/MetaMask/snaps/pull/3006))
|
|
30
|
+
|
|
10
31
|
## [9.16.0]
|
|
11
32
|
|
|
12
33
|
### Added
|
|
@@ -590,7 +611,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
590
611
|
- The version of the package no longer needs to match the version of all other
|
|
591
612
|
MetaMask Snaps packages.
|
|
592
613
|
|
|
593
|
-
[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.
|
|
614
|
+
[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.18.0...HEAD
|
|
615
|
+
[9.18.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.17.0...@metamask/snaps-controllers@9.18.0
|
|
616
|
+
[9.17.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.16.0...@metamask/snaps-controllers@9.17.0
|
|
594
617
|
[9.16.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.15.0...@metamask/snaps-controllers@9.16.0
|
|
595
618
|
[9.15.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.14.0...@metamask/snaps-controllers@9.15.0
|
|
596
619
|
[9.14.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@9.13.0...@metamask/snaps-controllers@9.14.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
|
|
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
|
-
|
|
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.
|
|
58
|
-
this.messagingSystem.subscribe('SnapController:snapDisabled', this.
|
|
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.
|
|
99
|
-
jobs?.forEach((job) => this.
|
|
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
|
|
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
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* @param job - Cronjob specification.
|
|
88
|
+
* @param backgroundEventWithoutId - Background event.
|
|
89
|
+
* @returns An id representing the background event.
|
|
111
90
|
*/
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
*
|
|
112
|
+
* Cancel a background event.
|
|
141
113
|
*
|
|
142
|
-
* @param
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
220
|
-
this.messagingSystem.unsubscribe('SnapController:snapDisabled', this.
|
|
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
|
|
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
|
-
|
|
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"]}
|