Files
BeWoPlaner/BeWoPlanerMobil/node_modules/rijs.sync/server.js
Lyndon Jetten fda2c306b5 MoK: Report Viewer hinzugefügt
FaC: Das alte Flag von Terminen, die in die Zeiterfassung übernommen worden sind, wird ebenfalls berücksichtigt.
2023-03-13 19:39:04 +01:00

223 lines
6.8 KiB
JavaScript

// -------------------------------------------
// Synchronises resources between server/client
// -------------------------------------------
module.exports = function sync(
ripple
, { server
, certs
, port = 5000
, max = 1000
} = {}
, { xrs = require('xrs/server')
} = {}
){
ripple.server = xrs({
processor: processor(ripple)
, serialise: serialise(ripple)
, connected
, certs
, port
}, { http: server })
// TODO: this should be populated by modules
ripple.server.blacklist = ['loaded', 'loading', 'transpile', 'from', 'path', 'flattened', 'vary']
ripple.server.cache = new LRUMap(max)
ripple
.on('change')
// TODO: need multi-key lru/lfuda cache, or restore multi-level cache
.map(([name]) => {
for (const key of ripple.server.cache.keys()) {
if (key.split(',').some(is(`name:${name}`)))
ripple.server.cache.delete(key)
}
})
return ripple
}
const processor = ripple => (req, res) => {
let reply
return req.binary ? req.socket.uploads[req.data.meta.index].emit('file', req)
: req.data.type == 'PREUPLOAD' ? upload(ripple, req, res)
: (reply = xres(ripple, req, res)) ? reply
: req.data.type == 'SUBSCRIBE' ? subscribe(ripple, req, res)(ripple.resources[req.data.name])
: false
}
const serialise = ripple => (message, socket, next) => {
if (!message.data.name) return next(message)
const res = ripple.resources[message.data.name]
, { vary = noop } = res.headers
, { cache } = socket.server
, key = vary(res, socket, message)
if (!key) return next(message)
if (cache.has(key)) return wrap(message.id, cache.get(key))
deb('cache miss'.yellow, key, cache.size)
const serialised = next(compress(message.data))
cache.set(key, serialised)
return wrap(message.id, serialised)
}
const wrap = (id, change) => id
? `{"id":"${id}","data":${change}}`
: `{"data":${change}}`
const compress = change => {
delete change.key
delete change.type
return change
}
const xres = (ripple, req, res) => {
// TODO: this could be multiple resources?
const { headers } = ripple.load(req.data.name)
, { from = noop } = headers
return unpromise(from(req, res))
}
const subscribe = (ripple, { data, socket }) => ({ name, body, headers } = {}) => ripple
.on('change')
.on('start', function(){
socket.subscriptions.named[name] = 1
this.next(data.value
? [name, { type: 'update', key: data.value, value: key(data.value)(body) }]
: [name, { type: 'update', value: body, headers }]
)
})
.filter(([n]) => n == name)
.map(arr => arr[1])
.each(dependencies(ripple, data.name, socket)) // TODO: plugin (or opt?)
.map(transpile(ripple, data.name, socket)) // TODO: plugin (or opt?)
.filter(by('key', subset(data.value)))
.map(format(name, data))
.map(strip(ripple.server.blacklist))
.unpromise()
const format = (name, data) => (change) => {
// TODO: separate change and resource here? or combine name into change?
// if (name !== data.name) change.name = name
change.name = name // TODO: can use id and not always set name
change.key = str(change.key).replace(data.value, '')
return change
}
// TODO: rename module?
// TODO: put back resource level type transforms
const dependencies = ({ dir, server, resources }, name, socket) => async (change, i, n) => {
let { headers } = resources[name]
, type = 'SUBSCRIBE'
, deps
, pushed =
!headers.dependencies ? 0
: !(deps = unsent(headers, socket)).length ? (change.headers = headers)
: (deps.map(name => server.recv(socket, { type, name })), change.headers = headers)
n.next(change)
}
const unsent = ({ flattened }, socket) => flattened
.filter(name => !socket.subscriptions.named[name])
const transpile = ({ resources }, name, { platform }, limit) => change => {
const { headers } = resources[name]
if (!(limit = key('transpile.limit')(headers)) || change.type !== 'update') return change
const browser = `${platform.name}-${platform.version}`
change.value = transform(change.value, platform)
return change
}
const transform = (thing, { name, version }) =>
is.fn(thing) ? fn(buble.transform(`(${thing})`, { target: { [name]: version, fallback }}).code)
: is.lit(thing) ? keys(thing)
.map(key => ({ key, val: transform(thing[key], { name, version }) }))
.reduce((p, v) => (p[v.key] = v.val, p), {})
: thing
const strip = list => change => {
if (!change.headers) return change
change.headers = keys(change.headers)
.filter(not(is.in(list)))
.reduce((p, k) => (p[k] = change.headers[k], p), {})
return change
}
const upload = async (ripple, req, res) => {
let uploads = req.socket.uploads = req.socket.uploads || {}
, { name, index, fields, size } = req.data
, upload = emitterify(uploads[index] = fields)
if (size) await upload
.on('file')
.map(file => (upload[file.data.meta.field][file.data.meta.i] = file.binary, file))
.reduce(((received = 0, file) => received += file.size))
.until(received => received == size)
delete uploads[index]
return processor(ripple)({
socket: req.socket
, data: {
name
, type: 'UPLOAD'
, value: upload
}
}, res)
}
const subset = (target = '') => (source = '') => str(source).startsWith(target)
const connected = socket => {
socket.platform = parse(socket.handshake.headers['user-agent'])
def(socket.subscriptions, 'named', {})
}
// TODO: factor out
const unpromise = d => (d && d.next ? d.unpromise() : d)
const major = (v, f) =>
v ? v.split('.').shift()
: includes('xp')(lo(f)) ? 'xp'
: '?'
const parse = useragent => {
let { name, version, os } = platform.parse(useragent)
name = lo(name)
version = major(version)
os = {
name: lo((os.family || '').split(' ').shift())
, version: major(os.version, os.family)
}
if (os.name == 'os') os.name == 'osx'
if (name == 'chrome mobile') name = 'chrome'
if (name == 'microsoft edge') name = 'edge'
return {
uid: `${name}-${version}-${os.name}-${os.version}`
, version
, name
, os
}
}
const by = require('utilise/by')
, is = require('utilise/is')
, lo = require('utilise/lo')
, fn = require('utilise/fn')
, str = require('utilise/str')
, not = require('utilise/not')
, key = require('utilise/key')
, def = require('utilise/def')
, keys = require('utilise/keys')
, noop = require('utilise/noop')
, values = require('utilise/values')
, includes = require('utilise/includes')
, emitterify = require('utilise/emitterify')
, deb = require('utilise/deb')('[ri/sync]')
, { LRUMap } = require('lru_map')
, platform = require('platform')
, buble = require('buble')
, fallback = { ie: 8, suppress: true }