Skip to content

Instantly share code, notes, and snippets.

@mr-mig
Last active November 22, 2019 09:40
Show Gist options
  • Save mr-mig/ac4cf8c258819945d3af2f3ee2616703 to your computer and use it in GitHub Desktop.
Save mr-mig/ac4cf8c258819945d3af2f3ee2616703 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
// Available variables:
// - Machine
// - interpret
// - assign
// - send
// - sendParent
// - spawn
// - raise
// - actions
// - XState (all XState exports)
const networkStates = {
initial: 'ready',
states: {
ready: {
on: {
FETCH_ENTITIES: 'fetching'
}
},
fetching: {
invoke: {
src: 'pullRemoteData',
onDone: {
target: 'ready',
actions: [
assign({
networkQueue: ctx => [],
pullResults: (ctx, event) => {
console.log(event, event.data)
return [...ctx.pullResults, event.data]
}
}),
send('PULL_NEXT')
]
},
onError: {
target: 'ready',
actions: [send('SYNC_FAILURE')]
}
}
}
}
}
const queueStates = {
initial: 'working',
states: {
working: {
invoke: {
src: 'pushingService',
onDone: 'cleared',
onError: 'failed'
},
on: {
'': {
target: 'cleared',
cond: 'isQueueEmpty'
},
}
},
failed: {
entry: send('SYNC_FAILURE')
},
cleared: {
on: {
'': '#app.sync.syncing.online.pushPull.started.pulling'
}
}
}
}
const pushingStates = {
initial: 'queue',
states: {
queue: queueStates
}
}
const scheduleRequest = request => ctx => {
return [...ctx.networkQueue, Promise.resolve(request)];
}
const pullState = (entity, nextEntity, parallel) => {
return {
on: {
PULL_NEXT: nextEntity
},
initial: 'ready',
states: {
ready: {
after: {
5000: 'pulling'
}
},
pulling: {
entry: [
assign({
networkQueue: scheduleRequest(entity)
}),
parallel ? send('PULL_NEXT') : send('FETCH_ENTITIES')
]
}
}
}
}
const pullingStates = {
type: 'parallel',
states: {
pullingOrder: {
initial: 'ready',
states: {
ready: {
on: {
'START_PULLING': 'settings'
}
},
settings: pullState('settings', 'capabilities', true),
capabilities: pullState('capabilities', 'listGroups'),
listGroups: pullState('listGroups', 'lists'),
lists: pullState('lists', 'tasks'),
tasks: pullState('tasks', 'members', true),
members: pullState('members', 'taskSuggestions'),
taskSuggestions: pullState('taskSuggestions', 'finished'),
finished: {
entry: [send('SYNC_SUCCESS')],
type: 'final'
}
}
},
network: networkStates
}
}
const pushPullSyncStates = {
initial: 'started',
states: {
started: {
initial: 'pushing',
on: {
SYNC_SUCCESS: 'synced',
SYNC_FAILURE: 'errored',
// OFFLINE: '#app.sync.stopped.offline'
},
states: {
pushing: pushingStates,
pulling: pullingStates
}
},
synced: {
on: {
'' : 'periodicSync'
}
},
errored: {
entry: 'saveError',
on: {
'' : [
{ target: '#app.sync.blocked', cond: 'shouldBlockSync'},
{ target: 'backoff' }
]
}
},
backoff: {
on : {
// OFFLINE: '#app.sync.stopped.offline'
},
after: {
BACKOFF_INTERVAL: 'started'
},
exit: 'increaseBackoff'
},
periodicSync: {
on : {
// OFFLINE: '#app.sync.stopped.offline'
},
after: {
PERIODIC_SYNC_INTERVAL: 'started'
}
}
}
}
const realtimeSyncStates = {
initial: 'init',
states: {
init: {
on: {
'': 'connecting'
}
},
connecting: {
invoke: {
src: 'startLongpolling',
onDone: 'connected',
onError: 'disconnected'
},
},
connected: {
invoke: {
src: 'realtimeDataParser'
},
on: {
REALTIME_NETWORK_ERROR: 'disconnected',
REALTIME_DISCONNECT: 'disconnected',
}
},
disconnected: {
after: {
REALTIME_RETRY_INTERVAL: 'connecting'
}
}
}
}
const syncStates = {
initial: 'syncing',
states: {
syncing: {
initial: 'online',
on: {
STOP_SYNC: 'stopped',
},
states: {
online: {
on: {
'': {
target: '#app.sync.stopped',
cond: 'isOffline'
},
},
type: 'parallel',
states: {
pushPull: pushPullSyncStates,
realtime: realtimeSyncStates,
}
}
}
},
stopped: {
initial: 'init',
on: {
START_SYNC: 'syncing'
},
states: {
init: {
on : {
'' : {
target: 'offline',
cond: 'isOffline'
}
}
},
offline: {
on: {
ONLINE: '#app.sync.syncing'
},
after: {
60000: '#app.sync.syncing'
}
}
},
},
blocked: {
type: 'final'
},
}
}
const onlineStates = {
initial: 'init',
states: {
init: {
invoke: {
src: 'offlineListener'
},
initial: 'checkingConnectivity',
on: {
CHECK_REMOTE: '.checkingConnectivity'
},
states: {
checkingConnectivity: {
invoke: {
src: 'checkRemote',
onDone: {
actions: send('ONLINE')
},
onError: {
actions: send('OFFLINE')
}
},
on: {
ONLINE: 'online',
OFFLINE: 'offline'
}
},
online: {
},
offline: {
on: {
ONLINE: 'online'
},
after: {
ONLINE_CHECK_INTERVAL: 'checkingConnectivity'
}
}
}
},
halt: {
type: 'final'
}
}
}
const sync = Machine(
{
id: 'app',
// the initial context (extended state) of the statechart
context: {
syncToken: null, //prompt('Sync Token'),
sessionId: 'x-state-testing',
correlationVector: 'x-state-testing',
features: {},
periodicSyncInterval: 120 * 1000,
backoffInterval: 4*1000,
lastHTTPError: null,
onlineCheckInterval: 30*1000,
realtimeRetryInterval: 25*1000,
remoteOnlineCheckURL: 'https://to-do-cdn.microsoft.com/static-assets/online.txt',
queueState: [],
networkQueue: [],
pullResults: []
},
type: 'parallel',
states: {
sync: syncStates,
onlineCheck: onlineStates
}
},
{
actions: {
saveError: assign({
lastHTTPError: (ctx, event) => event.data && event.data.error
}),
increaseBackoff: assign({
backoffInterval: ctx => {
if(ctx.lastHTTPError && ctx.lastHTTPError.httpCode == 429){
return Math.max(ctx.backoffInterval, 300*1000)
} else {
return Math.min(ctx.backoffInterval * 2, 512 * 1000)
}
}
})
},
delays: {
PERIODIC_SYNC_INTERVAL: ctx => ctx.periodicSyncInterval,
BACKOFF_INTERVAL: ctx => ctx.backoffInterval,
ONLINE_CHECK_INTERVAL: ctx => ctx.onlineCheckInterval,
REALTIME_RETRY_INTERVAL: ctx => ctx.realtimeRetryInterval
},
guards: {
isOffline: ctx => !navigator.onLine,
shouldBlockSync: ctx => [
'DomainNotFound',
'MailboxNotEnabledForRESTAPI',
'MailboxNotSupportedForRESTAPI',
'RESTAPINotEnabledForComponentSharedMailbox'
].includes(ctx.lastHTTPError && ctx.lastHTTPError.errorCode),
isQueueEmpty: ctx => ctx.queueState.length === 0
},
services: {
offlineListener: ctx => callback => {
const sendOnline = () => callback('ONLINE');
const sendOffline = () => callback('CHECK_REMOTE');
window.addEventListener('online', sendOnline);
window.addEventListener('offline', sendOffline);
return () => {
window.removeEventListener('online', sendOnline);
window.removeEventListener('offline', sendOffline);
}
},
checkRemote: ctx =>
window.fetch(ctx.remoteOnlineCheckURL),
startLongpolling: ctx => Promise.resolve(),
realtimeDataParser: ctx => callback => {
// TODO: get XHR from ctx
// callback('REALTIME_TIEMOUT'),
// callback('REALTIME_DISCONNECT')
// callback('REALTIME_PARSED_DATA')
},
pushingService: ctx => {
// TODO
return Promise.resolve()
},
pullRemoteData: ctx => {
const tasks = ctx.networkQueue;
return Promise.all(tasks)
.then(data => {
console.log(data);
return data
})
}
}
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment