Skip to content

Instantly share code, notes, and snippets.

@wspl
Created June 13, 2018 05:19
Show Gist options
  • Save wspl/6fe8feced1eef4d67654631f70b12381 to your computer and use it in GitHub Desktop.
Save wspl/6fe8feced1eef4d67654631f70b12381 to your computer and use it in GitHub Desktop.
Synx - Electron application global state synchronization library (with mobx).
import { ipcMain, ipcRenderer, WebContents } from 'electron'
import { autorun, toJS } from 'mobx'
import { PLStore } from './PLStore'
enum SynxActionType {
Subscribe,
Update
}
interface ISynxAction {
type: SynxActionType
key: string
value: any
kvs: {key: string, value: any}[]
}
export interface ISynxInterface<T> {
sx_onRestore: (handler: () => void) => void
}
export interface ISynxPersistenceOptions {
keyAlias?: { [key: string]: string }
fileAlias?: string
exclude?: string[]
include?: string[]
}
export function Synxify<T>(constructor: { new(): T }, name: string, persistence?: ISynxPersistenceOptions): T & ISynxInterface<T> {
if (process.type === 'renderer') {
return SynxifyRenderer(constructor, name)
} else {
return SynxifyMain(constructor, name, persistence)
}
}
function SynxifyMain<T>(constructor: { new(): T }, name: string, persistence?: ISynxPersistenceOptions): T & ISynxInterface<T> {
const instance = new constructor() as T & ISynxInterface<T>
const keys = Object.keys(constructor.prototype.__mobxDecorators)
let store: PLStore
if (persistence) {
store = new PLStore(name, instance)
keys.forEach(key => {
if (persistence.include && persistence.include.indexOf(key) === -1) return
if (persistence.exclude && persistence.exclude.indexOf(key) > -1) return
store.addKeyMapping(key, (persistence.keyAlias && persistence.keyAlias[key]) || key)
})
store.load()
}
const clients = new Set<WebContents>()
let lastUpdateClient: WebContents
ipcMain.on(`synx::${name}`, (event, action: ISynxAction) => {
switch (action.type) {
case SynxActionType.Subscribe:
const sender: WebContents = event.sender
clients.add(sender)
sender.send(`synx::${name}`, <ISynxAction> {
type: SynxActionType.Update,
kvs: JSON.parse(JSON.stringify(keys.map(key => ({key, value: instance[key]}))))
})
break
case SynxActionType.Update:
lastUpdateClient = event.sender
instance[action.key] = action.value
break
}
})
keys.forEach(key => {
autorun(() => {
const value = toJS(instance[key])
store && store.save()
clients.forEach(wc => {
// avoid send update action to data provider
if (lastUpdateClient === wc) {
return
}
wc.send(`synx::${name}`, <ISynxAction> {
type: SynxActionType.Update,
key,
value: JSON.parse(JSON.stringify(value))
})
})
})
})
return instance
}
function SynxifyRenderer<T>(constructor: { new(): T }, name: string): T & ISynxInterface<T> {
let restoreHandler: () => void
const instance = new constructor() as T & ISynxInterface<T>
instance.sx_onRestore = (handler) => {
restoreHandler = handler
}
const keys = Object.keys(constructor.prototype.__mobxDecorators)
const updateSelfKey = new Set<string>()
ipcRenderer.send(`synx::${name}`, <ISynxAction>{type: SynxActionType.Subscribe})
ipcRenderer.on(`synx::${name}`, (event, action: ISynxAction) => {
switch (action.type) {
case SynxActionType.Update:
if (action.kvs) {
// first update
action.kvs.forEach(({key, value}) => {
updateSelfKey.add(key)
instance[key] = value
})
restoreHandler && restoreHandler()
keys.forEach(key => {
autorun(() => {
const value = toJS(instance[key])
if (updateSelfKey.has(key)) {
updateSelfKey.delete(key)
return
}
ipcRenderer.send(`synx::${name}`, <ISynxAction> {
type: SynxActionType.Update,
key,
value: JSON.parse(JSON.stringify(value))
})
})
})
} else {
updateSelfKey.add(action.key)
instance[action.key] = action.value
}
break
}
})
return instance
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment