224 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { createApp } from 'petite-vue'
 | |
| 
 | |
| import { domain, nopoll } from '@params'
 | |
| 
 | |
| type Config = {
 | |
| 	apiurl: string
 | |
| 	pollurl: string
 | |
| 	domain: string
 | |
| 	itemid: string
 | |
| 	sid: string
 | |
| 	iid: string
 | |
| 	nopoll: boolean
 | |
| }
 | |
| 
 | |
| export function config (api, polling): Config {
 | |
| 	const pu = polling ? `${api.path}/${polling.msgbase.join('/')}` : ''
 | |
| 	const urlparams = new URL(location.href).searchParams
 | |
| 	return { 
 | |
| 		apiurl: api.path, 
 | |
| 		pollurl: pu, 
 | |
| 		domain: domain,
 | |
| 		itemid: urlparams.get('id'),
 | |
| 		sid: getSid(),
 | |
| 		iid: createRandString(1),
 | |
| 		nopoll: nopoll
 | |
| 	}
 | |
| }
 | |
| 
 | |
| export const pvapp = {
 | |
| 	run(conf: Config) {
 | |
| 		appdata.conf = conf
 | |
| 		createApp(appdata).mount()
 | |
| 		if (appdata.conf.pollurl && !appdata.conf.nopoll) {
 | |
| 			appdata.poll()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const appdata = {
 | |
| 	$delimiters: ['{|', '|}'],
 | |
| 	conf: {} as Config,
 | |
| 	components: {},
 | |
| 	output: '',
 | |
| 	handle,
 | |
| 	poll,
 | |
| 	mounted(name: string) {
 | |
| 		console.log('app mounted: ', name)
 | |
| 	},
 | |
| 	Data
 | |
| }
 | |
| 
 | |
| // components
 | |
| // (move to separate module(s))
 | |
| 
 | |
| function Data(name, action, domain: string): object {
 | |
| 	domain = domain || this.conf.domain
 | |
| 	const comp = {
 | |
| 		name: name, // -> class of incoming/outgoing message
 | |
| 		action: action, // (default) action of outgoing message
 | |
| 		domain: domain, // domain of incoming/outgoing message
 | |
| 		data: {}, 	// model (2-way data store)
 | |
| 		data_bak: {}, // backup copy when leaving view or edit mode
 | |
| 		meta: {},	// metadata (params) for each data field
 | |
| 		mode: 'view',
 | |
| 		mounted,
 | |
| 		chmode,
 | |
| 		copynew,
 | |
| 		exec		// default function to execute upon button click
 | |
| 	}
 | |
| 	this.components[name] = comp
 | |
| 	return comp
 | |
| }
 | |
| 
 | |
| // appdata and component method definitions
 | |
| 
 | |
| function mounted(name: string, meta: any) {
 | |
| 	if (meta.defexpr) {
 | |
| 		meta.default = eval(meta.defexpr)
 | |
| 	}
 | |
| 	this.data[name] = meta.default || ""
 | |
| 	this.meta[name] = meta
 | |
| }
 | |
| 
 | |
| function chmode(action: string) {
 | |
| 	if (this.mode == action) {
 | |
| 		return
 | |
| 	}
 | |
| 	if (this.mode == 'view') {
 | |
| 		Object.assign(this.data_bak, this.data)
 | |
| 	}
 | |
| 	switch(action) {
 | |
| 		case 'query':
 | |
| 			setQuery(this)
 | |
| 			break
 | |
| 		case 'new':
 | |
| 			setNew(this)
 | |
| 			break
 | |
| 		default:
 | |
| 			Object.assign(this.data, this.data_bak)
 | |
| 	}
 | |
| 	this.mode = action
 | |
| }
 | |
| 
 | |
| function copynew(action: string) {
 | |
| 	Object.assign(this.data, this.data_bak)
 | |
| 	this.data.id = ''
 | |
| 	this.mode = 'new'
 | |
| }
 | |
| 
 | |
| function exec(action: string) {
 | |
| 	action = action || this.action
 | |
| 	const data = this.data
 | |
| 	const conf = this.conf
 | |
| 	let value = ''
 | |
| 	for (const k of Object.keys(data)) {
 | |
| 		value += `${k}: ${data[k]}, `
 | |
| 	}
 | |
| 	this.output += '\n' + value
 | |
| 	console.log('exec:', value)
 | |
| 	sendMsg(conf, [this.domain, action, this.name, data.id], data)
 | |
| }
 | |
| 
 | |
| function handle(msg) {
 | |
| 	const data = JSON.parse(msg.payload)
 | |
| 	const [ domain, action, class_, item ] = msg.path.split('/')
 | |
| 	// TODO: check action => select handler fct
 | |
| 	const comp = this.components[class_ || 'data']
 | |
| 	if (domain && domain != comp.domain) {
 | |
| 		return
 | |
| 	}
 | |
| 	if (comp) {
 | |
| 		Object.assign(comp.data, data)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function poll() {
 | |
| 	dopoll(this)
 | |
| }
 | |
| 
 | |
| // helper functions for data manipulation
 | |
| 
 | |
| function setQuery(obj) {
 | |
| 	for (const key in obj.data) {
 | |
| 		obj.data[key] = ''
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function setNew(obj) {
 | |
| 	for (const key in obj.data) {
 | |
| 		obj.data[key] = ''
 | |
| 		const meta = obj.meta[key]
 | |
| 		if (meta) {
 | |
| 			obj.data[key] = meta.default || ''
 | |
| 		}
 | |
| 	}
 | |
| 	obj.data.id = ''
 | |
| }
 | |
| 
 | |
| // basic functions - move to api.ts
 | |
| 
 | |
| async function sendMsg(conf: Config, basemsg: string[], data: any) {
 | |
| 	const url = `${conf.apiurl}/${basemsg.join('/')}`
 | |
| 	await send(url, conf, data)
 | |
| }
 | |
| 
 | |
| async function send(url: string, conf: Config, data: any) {
 | |
| 	data._interaction = conf.iid
 | |
| 	const body = JSON.stringify(data)
 | |
| 	const headers = {}
 | |
| 	headers['X-Integrator-Session'] = conf.sid
 | |
| 	return fetch(url, {
 | |
| 		method: 'POST',
 | |
| 		headers: headers,
 | |
| 		body: body
 | |
| 	})
 | |
| }
 | |
| 
 | |
| function createRandString(size: number): string {
 | |
| 	const arr = new Uint32Array(size)
 | |
| 	crypto.getRandomValues(arr)
 | |
| 	const result: string[] = []
 | |
| 	arr.forEach((x) => result.push(x.toString(36)))
 | |
| 	return result.join('')
 | |
| }
 | |
| 
 | |
| function getSid(): string {
 | |
| 	const sid_key = 'api.sessionid'
 | |
| 	let sid = localStorage.getItem(sid_key)
 | |
| 	if (!sid) {
 | |
| 		sid = createRandString(2)
 | |
| 		localStorage.setItem(sid_key, sid)
 | |
| 	}
 | |
| 	return sid
 | |
| }
 | |
| 
 | |
| //console.log("sid: ", getSid())
 | |
| // TODO: clear sid - when?
 | |
| //localStorage.setItem('api.sessionid', '')
 | |
| 
 | |
| async function dopoll(app: typeof appdata) {
 | |
| 	const wait_time = 10000
 | |
| 	while (true) {
 | |
| 		try {
 | |
| 			const res = await(send(app.conf.pollurl, app.conf, {}))
 | |
| 			const msg = await res.json()
 | |
| 			console.log(msg)
 | |
| 			switch (msg.status) {
 | |
| 				case 'idle':
 | |
| 					break
 | |
| 				case 'data':
 | |
| 					app.handle(msg)
 | |
| 					break
 | |
| 				default:
 | |
| 					console.log('poll response: ', msg)
 | |
| 					await sleep(wait_time)
 | |
| 			}
 | |
| 		} catch (error) {
 | |
| 			console.log(error)
 | |
| 			await sleep(wait_time)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const sleep = (delay: number) => new Promise(r => setTimeout(r, delay))
 |