diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/jest.config.js | 3 | ||||
-rw-r--r-- | client/package.json | 1 | ||||
-rw-r--r-- | client/src/config.model.ts | 2 | ||||
-rw-r--r-- | client/src/database-connection.model.ts | 3 | ||||
-rw-r--r-- | client/src/database-connection.ts | 120 | ||||
-rw-r--r-- | client/src/index.ts | 1 | ||||
-rw-r--r-- | client/tsconfig.json | 4 | ||||
-rw-r--r-- | client/yarn.lock | 8 |
8 files changed, 121 insertions, 21 deletions
diff --git a/client/jest.config.js b/client/jest.config.js index 758fa13..6f79391 100644 --- a/client/jest.config.js +++ b/client/jest.config.js @@ -1,4 +1,5 @@ module.exports = { preset: 'ts-jest', - testEnvironment: 'node' + testEnvironment: 'node', + modulePathIgnorePatterns: ['dist'] } diff --git a/client/package.json b/client/package.json index b7489f0..66ea261 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,7 @@ "devDependencies": { "@types/jest": "^23.3.13", "@types/node": "^10.12.18", + "@types/ws": "^6.0.1", "jest": "^23.6.0", "ts-jest": "^23.10.5", "ts-node": "^7.0.1", diff --git a/client/src/config.model.ts b/client/src/config.model.ts index 278f7f7..09925bf 100644 --- a/client/src/config.model.ts +++ b/client/src/config.model.ts @@ -1,4 +1,4 @@ export interface Config { - wsPort: number + wsUrl: string httpUrl: string } diff --git a/client/src/database-connection.model.ts b/client/src/database-connection.model.ts index 0523e22..86999be 100644 --- a/client/src/database-connection.model.ts +++ b/client/src/database-connection.model.ts @@ -1,7 +1,8 @@ export interface DatabaseConnection { init(): Promise<any> - close(): Promise<any> + close(): any subscribe(path: string, callback: (e: any) => any): Promise<() => any> write(path: string, toWrite: Object): Promise<any> + read(path: string): Promise<any> remove(path: string): Promise<any> } diff --git a/client/src/database-connection.ts b/client/src/database-connection.ts index 3cf733c..f37b298 100644 --- a/client/src/database-connection.ts +++ b/client/src/database-connection.ts @@ -1,7 +1,28 @@ import { Config } from './config.model' +import { getKey } from '../../lib/util' +import { encodePath } from '../../lib/path' import { DatabaseConnection } from './database-connection.model' +import { DatabaseChange } from '../../lib/database-change.model' +import { Body } from 'node-fetch' -const IS_NODE = true +import WebSocket from 'ws' + +const objectToQuery = (query: Object): string => + Object.values(query).reduce( + (acc: string, [key, val]: [string, string]) => acc + `${key}=${val}&`, + '?' + ) + +// each path has one or more callbacks +// that must be fired with each change +interface CallbackTable { + [path: string]: CallbackRef[] +} + +interface CallbackRef { + id: string + callback: (e: Object) => void +} /** * Client side implementation is implemented using @@ -10,31 +31,100 @@ const IS_NODE = true */ export const dbFactory = (config: Config): DatabaseConnection => { // TODO figure out how to patch - // in native fetch instead + // in native fetch/websocke instead // when this is running in the browser - const send = require('node-fetch') + const nodeFetch = require('node-fetch') + const WebSocket = require('ws') + + let ws: WebSocket | null + let subCallbacks: CallbackTable = {} + + const send = (method: string) => ( + route: string, + body: Object | void + ): Promise<Body> => { + const isGet = method === 'GET' + if (isGet && body) { + route += objectToQuery(body) + } + return nodeFetch(route, { + method, + ...(isGet ? undefined : { body: JSON.stringify(body) }), + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + } + }) + } + + const hget = (route: string, body: Object | void) => + send('GET')(route, body).then((r: any) => r.json()) + const hpost = send('POST') + const write = (path: string, toWrite: Object | null) => { const { httpUrl } = config - const body = JSON.stringify({ + return hpost(`${httpUrl}/write`, { path, toWrite }) - return send(`${httpUrl}/write`, { - method: 'POST', - body, - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json' + } + + const read = (path: string): Promise<any> => { + const { httpUrl } = config + return hget(`${httpUrl}/read/${encodePath(path)}`) + } + + const socketMessageHandler = (event: { + data: any + type: string + target: WebSocket + }) => { + const dbChange: DatabaseChange = JSON.parse(event.data) + const callbackRefs = subCallbacks[dbChange.path] + if (callbackRefs && callbackRefs.length) { + for (let ref of callbackRefs) { + ref.callback(dbChange.change) } - }) + } } + return { - async init() {}, - async close() {}, - async subscribe(path: string, callback: (e: any) => Promise<any>) { - return () => {} + init() { + return new Promise(resolve => { + ws = new WebSocket(config.wsUrl) + // @ts-ignore + ws.addEventListener('message', socketMessageHandler) + // @ts-ignore + ws.addEventListener('open', () => resolve()) + }) + }, + close() { + if (ws) { + ws.removeEventListener('message', socketMessageHandler) + ws.close() + ws = null + } + }, + async subscribe(path: string, callback: (e: Object) => void) { + const id = getKey('callbackref') + const ref: CallbackRef = { + id, + callback + } + if (!subCallbacks[path]) { + const { httpUrl } = config + subCallbacks[path] = [ref] + await hpost(`${httpUrl}/subscribe`, { path }) + } else subCallbacks[path].push(ref) + // return a fucntion + // to remove our new sub from + // the table + return () => { + subCallbacks[path] = subCallbacks[path].filter(k => k.id !== id) + } }, write, + read, async remove(path: string) { write(path, null) } diff --git a/client/src/index.ts b/client/src/index.ts index f11e59c..d2a9d21 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -1,4 +1,3 @@ export { dbFactory } from './database-connection' export { DatabaseConnection } from './database-connection.model' export { Config } from './config.model' -declare var fetch: any diff --git a/client/tsconfig.json b/client/tsconfig.json index 1b31a6d..abe6846 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -34,8 +34,8 @@ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noUnusedLocals": true /* Report errors on unused locals. */, + "noUnusedParameters": true /* Report errors on unused parameters. */, // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ diff --git a/client/yarn.lock b/client/yarn.lock index 97a7de5..b29c5e1 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -89,6 +89,14 @@ "@types/glob" "*" "@types/node" "*" +"@types/ws@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28" + integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q== + dependencies: + "@types/events" "*" + "@types/node" "*" + abab@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" |