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))
|