wxt
wxt copied to clipboard
@wxt-dev/alarms Proposal
@wxt-dev/alarms API Proposal
Overview
This proposal outlines a simple wrapper for Chrome’s native browser.alarms API, available as @wxt-dev/alarms. The goal is to provide an asynchronous, promise-based interface that aligns with our existing API design patterns. The new design centers on a single function, defineAlarm, which returns an object with helper methods to manage an alarm instance.
Objectives
- Consistency: Provide a uniform, promise-based API similar to our other modules.
- Simplicity: Encapsulate alarm management within a defined object.
- Extensibility: Lay the groundwork for future enhancements, such as snapshotting and restoration of alarms.
Proposed API Design
1. defineAlarm
This function defines an alarm, automatically initializing and registering it if it does not already exist. It returns an object with methods to retrieve, reschedule, clear, and watch the alarm. By handling the registration of the alarm listener internally, this design eliminates the need for manually adding a listener via browser.alarms.onAlarm.addListener for each alarm, thereby reducing boilerplate and potential errors.
/**
* Defines an alarm with a specified name and configuration.
* Returns an object with helper methods to manage the alarm instance.
*
* @param {string} name - The name of the alarm.
* @param {browser.alarms.AlarmCreateInfo} options - The scheduling options for the alarm.
* @returns {DefinedAlarm} An object representing the defined alarm.
*/
function defineAlarm(name: string, options: browser.alarms.AlarmCreateInfo): DefinedAlarm;
The returned DefinedAlarm object has the following interface:
interface DefinedAlarm {
/**
* Retrieves the current alarm configuration.
*
* @returns {Promise<browser.alarms.Alarm | null>} A promise that resolves to the alarm object, or null if not found.
*/
getAlarm(): Promise<browser.alarms.Alarm | null>;
/**
* Reschedules the alarm with new scheduling options.
*
* @param {browser.alarms.AlarmCreateInfo} options - The new scheduling options.
* @returns {Promise<void>} A promise that resolves once the alarm is rescheduled.
*/
reschedule(options: browser.alarms.AlarmCreateInfo): Promise<void>;
/**
* Clears the alarm. Note: This only temporarily disables the alarm until the extension is reloaded or `defineAlarm` is run again.
*
* @returns {Promise<boolean>} A promise that resolves to true if the alarm was cleared successfully.
*/
clear(): Promise<boolean>;
/**
* Registers a callback to listen for alarm triggers.
*
* @param {(alarm: browser.alarms.Alarm) => void} callback - The callback function to invoke when the alarm triggers.
* @returns {() => void} A function to unwatch the alarm.
*/
watch(callback: (alarm: browser.alarms.Alarm) => void): () => void;
}
Example Usage
import { alarms } from '@wxt-dev/alarms';
// Define a new alarm and store it in a variable
const dailyReminder = alarms.defineAlarm('dailyReminder', { delayInMinutes: 1440, periodInMinutes: 1440 });
// Retrieve the alarm details when needed
const alarmDetails = await dailyReminder.getAlarm();
console.log('Alarm details:', alarmDetails);
// Reschedule the alarm if necessary
await dailyReminder.reschedule({ delayInMinutes: 1440, periodInMinutes: 1440 });
// Set up a listener for the alarm trigger
const unwatch = dailyReminder.watch((triggeredAlarm) => {
console.log('Alarm triggered:', triggeredAlarm);
});
// Clear the alarm when needed (this temporarily disables the alarm until the extension is reloaded or `defineAlarm` is run again)
await dailyReminder.clear();
// Remove the watch listener if it's no longer required
unwatch();
Alternative Scheduling Options
The API is designed with flexibility in mind. In addition to using browser.alarms for scheduling, it can be extended to support alternative scheduling mechanisms such as cron-based scheduling or even setInterval for simpler use cases. Developers may configure the scheduling method via options, enabling a more versatile approach to alarm management.
Why is this API Beneficial?
Using the traditional approach, you would need to manually add an event listener for each alarm, as shown below:
browser.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'dailyReminder') {
// Handle the alarm trigger
console.log('Alarm triggered:', alarm);
}
});
This approach requires you to check the alarm name each time an alarm is triggered. With our proposed API, this boilerplate code is encapsulated within the defineAlarm method. The API automatically registers a listener for the specified alarm and provides a dedicated watch method, simplifying your code to just:
const unwatch = dailyReminder.watch((triggeredAlarm) => {
console.log('Alarm triggered:', triggeredAlarm);
});
This not only improves code clarity and maintainability but also reduces the risk of errors from manual listener management.
Conclusion
This proposal outlines a straightforward and useful interface for working with Chrome alarms in an asynchronous and consistent manner. By returning an object from defineAlarm, developers can manage a specific alarm instance with its own methods, improving code clarity and encapsulation. This approach streamlines alarm management and reduces boilerplate code, making it easier to integrate and maintain alarm functionality within the extension.
That's awesome, i'm looking forward to it!
Nice, I like it! Couple of questions:
- Should
createbe namedupdateorreschedule? SincedefineAlarmalready "creates" the alarm? - I always find I need CRON support, is that something we should consider now while designing the API or build it into the initial version?
- What other alarm based packages are there, what problems do they solve, what can we learn from them, and how does this new package improve them? Also, we should consider the APIs of other popular, non extension, job scheduling packages.
@webext-core/job-scheduler- How does splitting the alarm definition and the on trigger callback into two calls provide more value than providing both together in a single object?cronalso makes you define the on trigger listener at the same time as the config for when it should fire.
Nice, I like it! Couple of questions:
* Should `create` be named `update` or `reschedule`? Since `defineAlarm` already "creates" the alarm? * I always find I need CRON support, is that something we should consider now while designing the API or build it into the initial version? * What other alarm based packages are there, what problems do they solve, what can we learn from them, and how does this new package improve them? Also, we should consider the APIs of other popular, non extension, job scheduling packages. * [`@webext-core/job-scheduler`](https://webext-core.aklinker1.io/job-scheduler/installation/) - How does splitting the alarm definition and the on trigger callback into two calls provide more value than providing both together in a single object? * [`cron`](https://www.npmjs.com/package/cron) also makes you define the on trigger listener at the same time as the config for when it should fire.
- I think
reschedulesounds a bit better yeah since that's effectively what you'd be doing. - I was actually thinking that it might be useful to support
cronetc. in the options so I'm open to incorporate that on the implementation so far it'd bealarms,cron, plainsetInterval - I haven't done any research in that regard just thought that an official solution would be good as either a built-in or package
When calling defineAlarm, if it already exists, will the AlarmCreateInfo update that alarm or will I have to do that myself in code via my own comparison (calling reschedule to adjust it) ?
It seems other libraries all only support a single listener per alarm, and that listener is a part of the definition. In your example API, we have to call a function to add a listener.
While this means you can have multiple listeners... In practice, has anyone ever needed two listeners for an alarm?
const dailyReminder = alarms.defineAlarm('dailyReminder', {
delayInMinutes: 1440,
periodInMinutes: 1440,
});
alarms.watch(() => { ... })
// vs
const dailyReminder = alarms.defineAlarm('dailyReminder', {
delayInMinutes: 1440,
periodInMinutes: 1440,
onTrigger: () => { ... },
});
I also don't like the name "watch" for it. If we keep the separate function call, it should probably be named onTrigger.
And here's an example use-case from one of my extensions to think about.
I have a daily alarm that goes off where I send a notification to a user about some daily goal that they've set up in my extension.
I would use a cron job that is set up to fire at a specific time of day based on a user setting.
During development, I also need a way to manually fire this notification so I don't have to wait until a specific time of day to run that code.