@oada/client
Advanced tools
Comparing version 1.2.0-alpha to 2.0.0
@@ -9,3 +9,2 @@ import { WebSocketClient } from "./websocket"; | ||
_ws?: WebSocketClient; | ||
pingInterval?: number; | ||
} | ||
@@ -53,2 +52,4 @@ export declare type Response = SocketResponse; | ||
private _ws; | ||
private _watchList; | ||
private _renewedReqIdMap; | ||
constructor(config: Config); | ||
@@ -59,2 +60,3 @@ clone(token: string): OADAClient; | ||
disconnect(): Promise<void>; | ||
awaitConnection(): Promise<void>; | ||
get(request: GETRequest): Promise<Response>; | ||
@@ -61,0 +63,0 @@ watch(request: WatchRequest): Promise<string>; |
@@ -14,4 +14,7 @@ "use strict"; | ||
const ksuid_1 = __importDefault(require("ksuid")); | ||
const debug_1 = __importDefault(require("debug")); | ||
const utils = __importStar(require("./utils")); | ||
const websocket_1 = require("./websocket"); | ||
const trace = debug_1.default("@oada/client:client:trace"); | ||
const error = debug_1.default("@oada/client:client:error"); | ||
class OADAClient { | ||
@@ -25,3 +28,21 @@ constructor(config) { | ||
this._concurrency = config.concurrency || this._concurrency; | ||
this._ws = new websocket_1.WebSocketClient(this._domain, this._concurrency, config.pingInterval); | ||
this._watchList = new Map(); | ||
this._renewedReqIdMap = new Map(); | ||
this._ws = new websocket_1.WebSocketClient(this._domain, this._concurrency); | ||
this._ws.on("open", async () => { | ||
const prevWatchList = this._watchList; | ||
this._watchList = new Map(); | ||
for (const [oldRequestId, watchRequest] of prevWatchList.entries()) { | ||
const newRequestId = await this.watch(watchRequest); | ||
const originalRequestId = this._renewedReqIdMap.get(oldRequestId); | ||
if (originalRequestId) { | ||
this._renewedReqIdMap.set(newRequestId, originalRequestId); | ||
this._renewedReqIdMap.delete(oldRequestId); | ||
} | ||
else { | ||
this._renewedReqIdMap.set(newRequestId, oldRequestId); | ||
} | ||
trace(`Update requestId: ${oldRequestId} -> ${newRequestId}`); | ||
} | ||
}); | ||
} | ||
@@ -46,2 +67,5 @@ clone(token) { | ||
} | ||
awaitConnection() { | ||
return this._ws.awaitConnection(); | ||
} | ||
async get(request) { | ||
@@ -82,4 +106,21 @@ const topLevelResponse = await this._ws.request({ | ||
}, (resp) => { | ||
var _a; | ||
for (const change of resp.change) { | ||
request.watchCallback(change); | ||
if (change.path === "") { | ||
const watchRequest = this._watchList.get(resp.requestId[0]); | ||
if (watchRequest) { | ||
const newRev = (_a = change.body) === null || _a === void 0 ? void 0 : _a["_rev"]; | ||
if (newRev) { | ||
watchRequest.rev = newRev; | ||
trace(`Updated the rev of request ${resp.requestId[0]} to ${newRev}`); | ||
} | ||
else { | ||
throw new Error("The _rev field is missing."); | ||
} | ||
} | ||
else { | ||
throw new Error("The original watch request does not exist."); | ||
} | ||
} | ||
} | ||
@@ -90,6 +131,17 @@ }, request.timeout); | ||
} | ||
return Array.isArray(r.requestId) ? r.requestId[0] : r.requestId; | ||
const requestId = Array.isArray(r.requestId) | ||
? r.requestId[0] | ||
: r.requestId; | ||
this._watchList.set(requestId, request); | ||
return requestId; | ||
} | ||
async unwatch(requestId) { | ||
return await this._ws.request({ | ||
let activeRequestId = requestId; | ||
for (const [currentRequestId, originalRequestId,] of this._renewedReqIdMap.entries()) { | ||
if (originalRequestId === requestId) { | ||
activeRequestId = currentRequestId; | ||
} | ||
} | ||
trace(`Unwatch requestId=${requestId}, actual=${activeRequestId}`); | ||
const response = await this._ws.request({ | ||
path: "", | ||
@@ -100,4 +152,9 @@ headers: { | ||
method: "unwatch", | ||
requestId: requestId, | ||
requestId: activeRequestId, | ||
}); | ||
if (!this._watchList.delete(activeRequestId)) { | ||
throw new Error("Could not find watch state information."); | ||
} | ||
this._renewedReqIdMap.delete(activeRequestId); | ||
return response; | ||
} | ||
@@ -160,3 +217,3 @@ async _recursiveGet(path, subTree, data) { | ||
data: linkObj, | ||
revIfMatch: 1, | ||
revIfMatch: resourceCheckResult.rev, | ||
}).catch((msg) => { | ||
@@ -163,0 +220,0 @@ if (msg.status == 412) { |
@@ -9,3 +9,5 @@ "use strict"; | ||
async function connect(config) { | ||
return new client_1.OADAClient(config); | ||
const client = new client_1.OADAClient(config); | ||
await client.awaitConnection(); | ||
return client; | ||
} | ||
@@ -12,0 +14,0 @@ exports.connect = connect; |
@@ -0,1 +1,3 @@ | ||
/// <reference types="node" /> | ||
import { EventEmitter } from "events"; | ||
import { Json, Change } from "."; | ||
@@ -5,3 +7,3 @@ export interface SocketRequest { | ||
path: string; | ||
method: "head" | "get" | "put" | "post" | "delete" | "watch" | "unwatch" | "ping"; | ||
method: "head" | "get" | "put" | "post" | "delete" | "watch" | "unwatch"; | ||
headers: Record<string, string>; | ||
@@ -23,3 +25,3 @@ data?: Json; | ||
} | ||
export declare class WebSocketClient { | ||
export declare class WebSocketClient extends EventEmitter { | ||
private _ws; | ||
@@ -30,12 +32,6 @@ private _domain; | ||
private _q; | ||
private _pingInterval; | ||
private _timeoutTimerID; | ||
private _pingTimerID; | ||
constructor(domain: string, concurrency?: number, pingInterval?: number); | ||
private _sendPing; | ||
private _resetReconnectTimers; | ||
private _reconnect; | ||
private _connect; | ||
constructor(domain: string, concurrency?: number); | ||
disconnect(): Promise<void>; | ||
isConnected(): boolean; | ||
awaitConnection(): Promise<void>; | ||
request(req: SocketRequest, callback?: (response: Readonly<SocketChange>) => void, timeout?: number): Promise<SocketResponse>; | ||
@@ -42,0 +38,0 @@ private doRequest; |
@@ -18,2 +18,4 @@ "use strict"; | ||
const WebSocket = require("isomorphic-ws"); | ||
const reconnecting_websocket_1 = __importDefault(require("reconnecting-websocket")); | ||
const events_1 = require("events"); | ||
const ksuid_1 = __importDefault(require("ksuid")); | ||
@@ -34,49 +36,29 @@ const p_queue_1 = __importDefault(require("p-queue")); | ||
})(ConnectionStatus || (ConnectionStatus = {})); | ||
class WebSocketClient { | ||
constructor(domain, concurrency = 10, pingInterval = 60000) { | ||
class WebSocketClient extends events_1.EventEmitter { | ||
constructor(domain, concurrency = 10) { | ||
super(); | ||
this._domain = domain; | ||
this._requests = new Map(); | ||
this._q = new p_queue_1.default({ concurrency }); | ||
this._q.on("active", () => { | ||
trace(`WS Queue. Size: ${this._q.size} pending: ${this._q.pending}`); | ||
}); | ||
this._status = ConnectionStatus.Connecting; | ||
this._ws = this._connect(); | ||
this._pingInterval = pingInterval; | ||
this._pingTimerID = setTimeout(this._sendPing.bind(this), this._pingInterval); | ||
this._timeoutTimerID = setTimeout(this._reconnect.bind(this), this._pingInterval + 5000); | ||
} | ||
_sendPing() { | ||
const pingRequest = { | ||
method: "ping", | ||
headers: { authorization: "" }, | ||
path: "", | ||
}; | ||
this.request(pingRequest); | ||
} | ||
_resetReconnectTimers() { | ||
clearTimeout(this._timeoutTimerID); | ||
clearTimeout(this._pingTimerID); | ||
this._pingTimerID = setTimeout(this._sendPing.bind(this), this._pingInterval); | ||
this._timeoutTimerID = setTimeout(this._reconnect.bind(this), this._pingInterval + 5000); | ||
} | ||
_reconnect() { | ||
this._ws = this._connect(); | ||
this._resetReconnectTimers(); | ||
} | ||
_connect() { | ||
this._status = ConnectionStatus.Connecting; | ||
return new Promise((resolve) => { | ||
const ws = new WebSocket("wss://" + this._domain, { | ||
origin: "https://" + this._domain, | ||
this._ws = new Promise((resolve) => { | ||
const ws = new reconnecting_websocket_1.default("wss://" + this._domain, [], { | ||
WebSocket, | ||
}); | ||
ws.onopen = () => { | ||
trace("Connection opened."); | ||
this._status = ConnectionStatus.Connected; | ||
resolve(ws); | ||
this.emit("open"); | ||
}; | ||
ws.onclose = () => { | ||
trace("Connection closed."); | ||
this._status = ConnectionStatus.Disconnected; | ||
this.emit("close"); | ||
}; | ||
ws.onmessage = this._receive.bind(this); | ||
}); | ||
this._q = new p_queue_1.default({ concurrency }); | ||
this._q.on("active", () => { | ||
trace(`WS Queue. Size: ${this._q.size} pending: ${this._q.pending}`); | ||
}); | ||
} | ||
@@ -92,2 +74,5 @@ async disconnect() { | ||
} | ||
async awaitConnection() { | ||
await this._ws; | ||
} | ||
request(req, callback, timeout) { | ||
@@ -128,3 +113,2 @@ return this._q.add(() => this.doRequest(req, callback, timeout)); | ||
_receive(m) { | ||
this._resetReconnectTimers(); | ||
try { | ||
@@ -143,3 +127,3 @@ const msg = JSON.parse(m.data.toString()); | ||
if (response_1.is(msg)) { | ||
if (!request.callback) { | ||
if (!request.persistent) { | ||
this._requests.delete(requestId); | ||
@@ -163,3 +147,3 @@ } | ||
const m = { | ||
requestId: msg.requestId, | ||
requestId: [requestId], | ||
resourceId: msg.resourceId, | ||
@@ -166,0 +150,0 @@ path_leftover: msg.path_leftover, |
{ | ||
"name": "@oada/client", | ||
"version": "1.2.0-alpha", | ||
"version": "2.0.0", | ||
"description": "A lightweight client tool to interact with an OADA-compliant server", | ||
@@ -20,3 +20,3 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@oada/types": "^1.2.0-beta.5", | ||
"@oada/types": "^1.0.6", | ||
"debug": "^4.1.1", | ||
@@ -26,2 +26,3 @@ "isomorphic-ws": "^4.0.1", | ||
"p-queue": "^6.4.0", | ||
"reconnecting-websocket": "^4.4.0", | ||
"ws": "^7.2.3" | ||
@@ -33,2 +34,3 @@ }, | ||
"@types/debug": "^4.1.5", | ||
"@types/events": "^3.0.0", | ||
"@types/mocha": "^7.0.2", | ||
@@ -35,0 +37,0 @@ "@types/node": "^13.13.4", |
@@ -17,4 +17,4 @@ # @oada/client | ||
```javascript | ||
var client = require("@oada/client") | ||
var connection = await client.connect({ | ||
const client = require("@oada/client") | ||
const connection = await client.connect({ | ||
domain: "api.oada.com", | ||
@@ -30,3 +30,3 @@ token: "abc" | ||
```javascript | ||
var response = await connection.get({ | ||
const response = await connection.get({ | ||
path: '/bookmarks/test', | ||
@@ -40,3 +40,3 @@ timeout: 1000 // timeout in milliseconds (optional) | ||
``` javascript | ||
var dataTree = { | ||
const dataTree = { | ||
"bookmarks": { | ||
@@ -57,3 +57,3 @@ "_type": "application/vnd.oada.bookmarks.1+json", | ||
} | ||
var response = await connection.get({ | ||
const response = await connection.get({ | ||
path: '/bookmarks/thing', | ||
@@ -67,4 +67,5 @@ tree: dataTree, | ||
A watch request can be issued by passing a callback function to `watchCallback` argument of a GET request. | ||
```javascript | ||
var response = await connection.get({ | ||
const response = await connection.get({ | ||
path: '/bookmarks/test', | ||
@@ -78,2 +79,19 @@ watchCallback: d => { | ||
Alternatively, one could explicitly send a `watch` request as follows. | ||
```javascript | ||
const requestId = await connection.watch({ | ||
path: '/bookmarks/test', | ||
rev: 1, // optional | ||
watchCallback: d => { | ||
console.log(d); | ||
}, | ||
timeout: 1000 // timeout in milliseconds (optional) | ||
}) | ||
``` | ||
To unwatch a resource, use the `unwatch` request. | ||
```javascript | ||
const response = await connection.unwatch(requestId); | ||
``` | ||
### PUT | ||
@@ -84,3 +102,3 @@ | ||
```javascript | ||
var response = await connection.put({ | ||
const response = await connection.put({ | ||
path: "/bookmarks/test", | ||
@@ -96,3 +114,3 @@ data: { thing: "abc" }, | ||
``` javascript | ||
var dataTree = { | ||
const dataTree = { | ||
"bookmarks": { | ||
@@ -113,3 +131,3 @@ "_type": "application/vnd.oada.bookmarks.1+json", | ||
} | ||
var response = await connection.put({ | ||
const response = await connection.put({ | ||
path: '/bookmarks/thing/abc/xyz/zzz', | ||
@@ -125,3 +143,3 @@ tree: dataTree, | ||
```javascript | ||
var response = await connection.head({ | ||
const response = await connection.head({ | ||
path: '/bookmarks/test', | ||
@@ -128,0 +146,0 @@ timeout: 1000 // timeout in milliseconds (optional) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
51865
17
765
1
139
7
14
+ Addedreconnecting-websocket@4.4.0(transitive)
Updated@oada/types@^1.0.6