Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@

import type { SnapRegistryController } from './SnapRegistryController';

/**
* Request a periodic update of the registry database.
*
* This useful for updating the database with a longer interval than the
* recent fetch threshold, which can help to reduce the number of fetches
* while still keeping the database reasonably up to date.
*/
export type SnapRegistryControllerRequestPeriodicUpdateAction = {
type: `SnapRegistryController:requestPeriodicUpdate`;
handler: SnapRegistryController['requestPeriodicUpdate'];
};

/**
* Triggers an update of the registry database.
*
Expand Down Expand Up @@ -49,6 +61,7 @@ export type SnapRegistryControllerGetMetadataAction = {
* Union of all SnapRegistryController action types.
*/
export type SnapRegistryControllerMethodActions =
| SnapRegistryControllerRequestPeriodicUpdateAction
| SnapRegistryControllerRequestUpdateAction
| SnapRegistryControllerGetAction
| SnapRegistryControllerResolveVersionAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,56 @@ describe('SnapRegistryController', () => {
});
});

describe('requestPeriodicUpdate', () => {
it('fetches the registry if it has never been fetched', async () => {
fetchMock
.mockResponseOnce(JSON.stringify(MOCK_DATABASE))
.mockResponseOnce(JSON.stringify(MOCK_SIGNATURE_FILE));

const { messenger } = getRegistry();
await messenger.call('SnapRegistryController:requestPeriodicUpdate');

expect(fetchMock).toHaveBeenCalledTimes(2);
});

it('skips the update if the registry was fetched within the periodic threshold', async () => {
const { messenger } = getRegistry({
state: {
lastUpdated: Date.now(),
database: MOCK_DATABASE,
signature: MOCK_SIGNATURE,
databaseUnavailable: false,
},
});

const listener = jest.fn();
messenger.subscribe('SnapRegistryController:registryUpdated', listener);

await messenger.call('SnapRegistryController:requestPeriodicUpdate');

expect(fetchMock).not.toHaveBeenCalled();
expect(listener).toHaveBeenCalledWith(false);
});

it('fetches the registry if the last update was older than the periodic threshold', async () => {
fetchMock
.mockResponseOnce(JSON.stringify(MOCK_DATABASE))
.mockResponseOnce(JSON.stringify(MOCK_SIGNATURE_FILE));

const { messenger } = getRegistry({
state: {
lastUpdated: 0,
database: MOCK_DATABASE,
signature: MOCK_SIGNATURE,
databaseUnavailable: false,
},
});
await messenger.call('SnapRegistryController:requestPeriodicUpdate');

expect(fetchMock).toHaveBeenCalledTimes(2);
});
});

describe('update', () => {
it('updates the database', async () => {
fetchMock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export type SnapRegistryControllerArgs = {
fetchFunction?: typeof fetch;
url?: JsonSnapsRegistryUrl;
recentFetchThreshold?: number;
periodicFetchThreshold?: number;
refetchOnAllowlistMiss?: boolean;
publicKey?: Hex;
clientConfig: ClientConfig;
Expand Down Expand Up @@ -102,6 +103,7 @@ const MESSENGER_EXPOSED_METHODS = [
'getMetadata',
'resolveVersion',
'requestUpdate',
'requestPeriodicUpdate',
] as const;

const defaultState = {
Expand All @@ -128,6 +130,8 @@ export class SnapRegistryController extends BaseController<

readonly #refetchOnAllowlistMiss: boolean;

readonly #periodicFetchThreshold: number;

#currentUpdate: Promise<void> | null;

constructor({
Expand All @@ -141,6 +145,7 @@ export class SnapRegistryController extends BaseController<
clientConfig,
fetchFunction = globalThis.fetch.bind(undefined),
recentFetchThreshold = inMilliseconds(5, Duration.Minute),
periodicFetchThreshold = inMilliseconds(4, Duration.Hour),
refetchOnAllowlistMiss = true,
}: SnapRegistryControllerArgs) {
super({
Expand Down Expand Up @@ -182,6 +187,7 @@ export class SnapRegistryController extends BaseController<
this.#clientConfig = clientConfig;
this.#fetchFunction = fetchFunction;
this.#recentFetchThreshold = recentFetchThreshold;
this.#periodicFetchThreshold = periodicFetchThreshold;
this.#refetchOnAllowlistMiss = refetchOnAllowlistMiss;
this.#currentUpdate = null;

Expand All @@ -191,13 +197,35 @@ export class SnapRegistryController extends BaseController<
);
}

#wasRecentlyFetched() {
/**
* Get whether the registry was recently fetched.
*
* @param threshold - The threshold in milliseconds to consider the registry
* as recently fetched.
* @returns Whether the registry was recently fetched.
*/
#wasRecentlyFetched(threshold = this.#recentFetchThreshold) {
return (
this.state.lastUpdated &&
Date.now() - this.state.lastUpdated < this.#recentFetchThreshold
this.state.lastUpdated && Date.now() - this.state.lastUpdated < threshold
);
}

/**
* Request a periodic update of the registry database.
*
* This useful for updating the database with a longer interval than the
* recent fetch threshold, which can help to reduce the number of fetches
* while still keeping the database reasonably up to date.
*/
async requestPeriodicUpdate() {
Comment thread
Mrtenz marked this conversation as resolved.
if (this.#wasRecentlyFetched(this.#periodicFetchThreshold)) {
this.messenger.publish('SnapRegistryController:registryUpdated', false);
return;
}
Comment thread
Mrtenz marked this conversation as resolved.

await this.requestUpdate();
}

/**
* Triggers an update of the registry database.
*
Expand All @@ -220,7 +248,7 @@ export class SnapRegistryController extends BaseController<
/**
* Updates the registry database if the registry hasn't been updated recently.
*
* NOTE: SHOULD NOT be called directly, instead `triggerUpdate` should be used.
* NOTE: SHOULD NOT be called directly, instead `requestUpdate` should be used.
*/
async #update() {
// No-op if we recently fetched the registry.
Expand Down
Loading