1import { createMachine, assign } from 'xstate'; 2 3export enum UpdatesStateMachineEventTypes { 4 CHECK = 'check', 5 CHECK_COMPLETE_AVAILABLE = 'checkCompleteAvailable', 6 CHECK_COMPLETE_UNAVAILABLE = 'checkCompleteUnavailable', 7 CHECK_ERROR = 'checkError', 8 DOWNLOAD = 'download', 9 DOWNLOAD_COMPLETE = 'downloadComplete', 10 DOWNLOAD_ERROR = 'downloadError', 11 RESTART = 'restart', 12} 13 14/** 15 * Simplified model for an update manifest 16 */ 17export type Manifest = { 18 updateId: string; 19}; 20 21/** 22 * Model for an update event 23 */ 24export type UpdatesStateMachineEvent = { 25 type: UpdatesStateMachineEventTypes; 26 body: { 27 message?: string; 28 manifest?: Manifest; 29 isRollBackToEmbedded?: boolean; 30 }; 31}; 32 33/** 34 * The context structure 35 */ 36export interface UpdatesStateMachineContext { 37 isUpdateAvailable: boolean; 38 isUpdatePending: boolean; 39 latestManifest?: Manifest; 40 isChecking: boolean; 41 isDownloading: boolean; 42 isRollback: boolean; 43 downloadedManifest?: Manifest; 44 checkError?: Error; 45 downloadError?: Error; 46} 47 48/** 49 * Actions that modify the context 50 */ 51const checkCompleteAvailableAction = assign({ 52 latestManifest: (context: UpdatesStateMachineContext, event: UpdatesStateMachineEvent) => 53 event.body?.manifest || undefined, 54 checkError: () => undefined, 55 isChecking: () => false, 56 isUpdateAvailable: () => true, 57 isRollback: (context: UpdatesStateMachineContext, event: UpdatesStateMachineEvent) => 58 Boolean(event.body?.isRollBackToEmbedded), 59}); 60 61const checkCompleteUnavailableAction = assign({ 62 latestManifest: () => undefined, 63 checkError: () => undefined, 64 isChecking: () => false, 65 isUpdateAvailable: () => false, 66 isRollback: () => false, 67}); 68 69const checkErrorAction = assign({ 70 isChecking: () => false, 71 checkError: (context: UpdatesStateMachineContext, event: UpdatesStateMachineEvent) => 72 new Error(event.body?.message || 'checkError'), 73}); 74 75const downloadCompleteAction = assign({ 76 downloadedManifest: (context: UpdatesStateMachineContext, event: UpdatesStateMachineEvent) => 77 event.body?.manifest || context.downloadedManifest, 78 latestManifest: (context: UpdatesStateMachineContext, event: UpdatesStateMachineEvent) => 79 event.body?.manifest || context.latestManifest, 80 downloadError: () => undefined, 81 isDownloading: () => false, 82 isUpdatePending: (context: UpdatesStateMachineContext, event: UpdatesStateMachineEvent) => 83 !!(event.body?.manifest || context.downloadedManifest), 84 isUpdateAvailable: (context: UpdatesStateMachineContext, event: UpdatesStateMachineEvent) => 85 event.body?.manifest !== undefined || context.isUpdateAvailable, 86}); 87 88const downloadErrorAction = assign({ 89 downloadError: (context: UpdatesStateMachineContext, event: UpdatesStateMachineEvent) => 90 new Error(event.body?.message || 'downloadError'), 91 isDownloading: () => false, 92}); 93 94const check = assign({ 95 isChecking: (context: UpdatesStateMachineContext) => true, 96}); 97 98const download = assign({ 99 isDownloading: (context: UpdatesStateMachineContext) => true, 100}); 101 102/** 103 * Model of the expo-updates state machine, written in Typescript. 104 * The actual implementations of this state machine will be in Swift on iOS and Kotlin on Android. 105 */ 106export const UpdatesStateMachine = createMachine<UpdatesStateMachineContext>({ 107 id: 'Updates', 108 initial: 'idle', 109 context: { 110 isChecking: false, 111 isDownloading: false, 112 isUpdateAvailable: false, 113 isUpdatePending: false, 114 isRollback: false, 115 }, 116 predictableActionArguments: true, 117 states: { 118 idle: { 119 on: { 120 check: { 121 target: 'checking', 122 actions: check, 123 }, 124 download: { 125 target: 'downloading', 126 actions: download, 127 }, 128 restart: { 129 target: 'restarting', 130 }, 131 }, 132 }, 133 checking: { 134 on: { 135 checkCompleteAvailable: { 136 target: 'idle', 137 actions: [checkCompleteAvailableAction], 138 }, 139 checkCompleteUnavailable: { 140 target: 'idle', 141 actions: [checkCompleteUnavailableAction], 142 }, 143 checkError: { 144 target: 'idle', 145 actions: [checkErrorAction], 146 }, 147 }, 148 }, 149 downloading: { 150 on: { 151 downloadComplete: { 152 target: 'idle', 153 actions: [downloadCompleteAction], 154 }, 155 downloadError: { 156 target: 'idle', 157 actions: [downloadErrorAction], 158 }, 159 }, 160 }, 161 restarting: { 162 type: 'final', 163 }, 164 }, 165}); 166