Skip to content

Commit

Permalink
feat: support force updating to latest version
Browse files Browse the repository at this point in the history
  • Loading branch information
FrederikBolding committed May 16, 2024
1 parent ef34b5d commit 6a2ed05
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 10 deletions.
53 changes: 53 additions & 0 deletions packages/snaps-controllers/src/snaps/SnapController.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6439,6 +6439,59 @@ describe('SnapController', () => {
controller.destroy();
});

it('updates to latest when latest version is specified', async () => {
const { manifest } = await getMockSnapFilesWithUpdatedChecksum({
manifest: getSnapManifest({
version: '1.1.0' as SemVerVersion,
}),
});

const location = new LoopbackLocation({
manifest: manifest.result,
});

location.resolveVersion.mockImplementation(
async () => '1.1.0' as SemVerRange,
);

const messenger = getSnapControllerMessenger();
const controller = getSnapController(
getSnapControllerOptions({
messenger,
state: {
snaps: getPersistedSnapsState(),
},
detectSnapLocation: loopbackDetect(location),
}),
);

const result = await controller.installSnaps(MOCK_ORIGIN, {
[MOCK_SNAP_ID]: { version: 'latest' },
});

const newSnapTruncated = controller.getTruncated(MOCK_SNAP_ID);

const newSnap = controller.get(MOCK_SNAP_ID);

expect(result[MOCK_SNAP_ID]).toStrictEqual(newSnapTruncated);
expect(newSnap?.version).toBe('1.1.0');
expect(newSnap?.versionHistory).toStrictEqual([
{
origin: MOCK_ORIGIN,
version: '1.0.0',
date: expect.any(Number),
},
{
origin: MOCK_ORIGIN,
version: '1.1.0',
date: expect.any(Number),
},
]);
expect(newSnap?.status).toBe(SnapStatus.Running);

controller.destroy();
});

it('requests approval for new and already approved permissions and revoke unused permissions', async () => {
const rootMessenger = getControllerMessenger();
const messenger = getSnapControllerMessenger(rootMessenger);
Expand Down
3 changes: 2 additions & 1 deletion packages/snaps-controllers/src/snaps/SnapController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2129,6 +2129,7 @@ export class SnapController extends BaseController<
// Existing snaps may need to be updated, unless they should be re-installed (e.g. local snaps)
// Everything else is treated as an install
const isUpdate = this.has(snapId) && !location.shouldAlwaysReload;
const forceLatest = rawVersion === 'latest';

if (isUpdate && this.#isValidUpdate(snapId, version)) {
const existingSnap = this.getExpect(snapId);
Expand All @@ -2148,7 +2149,7 @@ export class SnapController extends BaseController<
origin,
snapId,
location,
version,
forceLatest && isUpdate ? await location.resolveVersion() : version,
);
}

Expand Down
5 changes: 5 additions & 0 deletions packages/snaps-controllers/src/snaps/location/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
normalizeRelative,
parseJson,
} from '@metamask/snaps-utils';
import type { SemVerRange } from '@metamask/utils';
import { assert, assertStruct } from '@metamask/utils';

import type { SnapLocation } from './location';
Expand Down Expand Up @@ -109,6 +110,10 @@ export class HttpLocation implements SnapLocation {
return this.fetch(relativePath);
}

async resolveVersion(): Promise<SemVerRange> {
return '*' as SemVerRange;
}

get root(): URL {
return new URL(this.url);
}
Expand Down
5 changes: 5 additions & 0 deletions packages/snaps-controllers/src/snaps/location/local.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { SnapManifest, VirtualFile } from '@metamask/snaps-utils';
import { LocalSnapIdStruct, SnapIdPrefixes } from '@metamask/snaps-utils';
import type { SemVerRange } from '@metamask/utils';
import { assert, assertStruct } from '@metamask/utils';

import type { HttpOptions } from './http';
Expand Down Expand Up @@ -33,6 +34,10 @@ export class LocalLocation implements SnapLocation {
return convertCanonical(await this.#http.fetch(path));
}

async resolveVersion(): Promise<SemVerRange> {
return '*' as SemVerRange;
}

get shouldAlwaysReload() {
return true;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/snaps-controllers/src/snaps/location/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface SnapLocation {
manifest(): Promise<VirtualFile<SnapManifest>>;
fetch(path: string): Promise<VirtualFile>;

resolveVersion(): Promise<SemVerRange>;

readonly shouldAlwaysReload?: boolean;
}

Expand Down
37 changes: 28 additions & 9 deletions packages/snaps-controllers/src/snaps/location/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export interface NpmOptions {
export abstract class BaseNpmLocation implements SnapLocation {
protected readonly meta: NpmMeta;

#resolvedPackage: {
tarballURL: string;
targetVersion: SemVerVersion;
} | null = null;

#validatedManifest?: VirtualFile<SnapManifest>;

#files?: Map<string, VirtualFile>;
Expand Down Expand Up @@ -165,18 +170,32 @@ export abstract class BaseNpmLocation implements SnapLocation {
return this.meta.requestedRange;
}

async resolveVersion(): Promise<SemVerRange> {
if (!this.#resolvedPackage) {
const resolvedVersion = await this.meta.resolveVersion(
this.meta.requestedRange,
);

const resolved = await resolveNpmVersion(
this.meta.packageName,
resolvedVersion,
this.meta.registry,
this.meta.fetch,
);

this.#resolvedPackage = resolved;
}

return this.#resolvedPackage.targetVersion as unknown as SemVerRange;
}

async #lazyInit() {
assert(this.#files === undefined);
const resolvedVersion = await this.meta.resolveVersion(
this.meta.requestedRange,
);

const { tarballURL, targetVersion } = await resolveNpmVersion(
this.meta.packageName,
resolvedVersion,
this.meta.registry,
this.meta.fetch,
);
const targetVersion = await this.resolveVersion();

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { tarballURL } = this.#resolvedPackage!;

if (!isValidUrl(tarballURL) || !tarballURL.toString().endsWith('.tgz')) {
throw new Error(
Expand Down
4 changes: 4 additions & 0 deletions packages/snaps-controllers/src/test-utils/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ export class LoopbackLocation implements SnapLocation {
);
return file;
});

resolveVersion = jest.fn(async () => {
return '*' as SemVerRange;
});
/* eslint-enable @typescript-eslint/require-await */

get shouldAlwaysReload() {
Expand Down

0 comments on commit 6a2ed05

Please sign in to comment.