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