capacitor-updater icon indicating copy to clipboard operation
capacitor-updater copied to clipboard

`notifyAppReady()` should also be required in Manual update mode.

Open lincolnthree opened this issue 2 years ago • 0 comments

When performing manual updates, there is currently no protection against a failed/bad update.

I think we need the following changes/support:

Methods that return version information should also return an update status (success/error/pending):

  • success -> the update was downloaded, set, and launched successfully: notifyAppReady() was called for this version
  • error -> the update was downloaded and set, but notifyAppReady() was never called
  • pending -> the update was downloaded but never set() and never launched.
export interface VersionInfo {
    version: string;  // version identifier (random numbers returned by native plugin)
    status: 'success' | 'error' | 'pending'; // current status of this version
}

A configurable notifyAppReadyTimeout should be added to capacitor.config.ts (this is optional, but if not provided, the documentation should let the developer know how quickly this needs to be called):

// capacitor.config.ts
...
	"appId": "**.***.**",
	"appName": "Name",
	"plugins": {
		"CapacitorUpdater": {
			"notifyAppReadyTimeout": 10000 // <-- Milliseconds
		}
	}
...

** If notifyAppReady() is not called within the specified (or default value of) notifyAppReadyTimeout, the app should revert to the previous success version, or builtin if none was previously set: **

Let us consider the following workflow:

We perform the first download/set, as the current version is builtin, which can never be rolled back:

const builtin: VersionInfo = await CapacitorUpdater.current();
console.log(builtin);
// { version: 'builtin', status: 'success' }

const downloaded: VersionInfo = await CapacitorUpdater.download({  url: 'https://.../dist.zip' });
console.log(downloaded);
// { version: 'fdc2b4ae1', status: 'pending' }

const set: VersionInfo = await CapacitorUpdater.set('fdc2b4ae1');
console.log(set);
// { version: 'fdc2b4ae1', status: 'pending' } <--- status is still pending because app needs to reload

Now the app app is reloaded, and we call notifyAppReady() which returns the updated version info:

const current: VersionInfo = await CapacitorUpdater.notifyAppReady();
console.log(current);
// { version: 'fdc2b4ae1', status: 'success' }

The native code also stores a previous() version:

const previous: VersionInfo = await CapacitorUpdater.previous();
console.log(previous);
// { version: 'builtin', status: 'success' } <-- this should always return 'builtin' /  'success' if no previous version is set.

Now, for example's sake, we perform another manual update:

const info: VersionInfo = await CapacitorUpdater.download({  url: 'https://.../dist.zip' });
console.log(info);
// { version: 'b9a7dc3e5', status: 'pending' }

const info: VersionInfo = await CapacitorUpdater.set('b9a7dc3e5');
console.log(info);
// { version: 'b9a7dc3e5', status: 'pending' } <--- status is still pending because app needs to reload

Now the app app is reloaded, but notifyAppReady() is NOT called. The native plugin waits for notifyAppReadyTimeout milliseconds, then rolls back to previous version and reloads the application from native code.

The app starts successfully on the previous() version fdc2b4ae1 and notifyAppReady() is called again, but is a no-op for this version since it has already been confirmed successful:

const current: VersionInfo = await CapacitorUpdater.notifyAppReady();
console.log(current);
// { version: 'fdc2b4ae1', status: 'success' }

Listing all available version would return something like the following:

const downloads: VersionInfo[] = await CapacitorUpdater.list();
console.log(downloads);
// [
// { version: 'builtin', status: 'success' }
// { version: 'b9a7dc3e5', status: 'failed' }
// { version: 'fdc2b4ae1', status: 'success' }
// ]

Final note about fallback behavior: On the rare occurrence that the previous version fdc2b4ae1 ALSO fails to call notifyAppReady() within the specified notifyAppReadyTimeout, the app could be reverted to builtin, but this behavior is probably something that could probably be configurable in capacitor.config.ts:

const current: VersionInfo = await CapacitorUpdater.notifyAppReady(); console.log(current); // { version: 'builtin', status: 'success' }

	"plugins": {
		"CapacitorUpdater": {
			"allowRollbackToBuiltin": true
		}
	}

lincolnthree avatar Apr 22 '22 16:04 lincolnthree