@oada/client
Advanced tools
Comparing version 0.0.3 to 1.0.0
@@ -1,26 +0,33 @@ | ||
import { Response } from "./websocket"; | ||
import { WebSocketClient } from "./websocket"; | ||
import { SocketResponse } from "./websocket"; | ||
import { Json, Change } from "."; | ||
export interface Config { | ||
domain: string; | ||
options?: { | ||
redirect: string; | ||
metadata: string; | ||
scope: string; | ||
}; | ||
token?: string; | ||
concurrency?: number; | ||
_ws?: WebSocketClient; | ||
} | ||
export declare type Response = SocketResponse; | ||
export interface GETRequest { | ||
path: string; | ||
tree?: object; | ||
watchCallback?: (response: Response) => void; | ||
watchCallback?: (response: Readonly<Change>) => void; | ||
} | ||
export interface WatchRequest { | ||
path: string; | ||
watchCallback: (response: Response) => void; | ||
rev?: string; | ||
watchCallback: (response: Readonly<Change>) => void; | ||
} | ||
export interface PUTRequest { | ||
path: string; | ||
data: object; | ||
data: Json; | ||
contentType?: string; | ||
tree?: object; | ||
} | ||
export interface POSTRequest { | ||
path: string; | ||
data: Json; | ||
contentType?: string; | ||
tree?: object; | ||
} | ||
export interface HEADRequest { | ||
@@ -34,10 +41,16 @@ path: string; | ||
private _token; | ||
private _ws?; | ||
constructor(); | ||
connect(config: Config): Promise<void>; | ||
disconnect(): void; | ||
private _domain; | ||
private _concurrency; | ||
private _ws; | ||
constructor(config: Config); | ||
clone(token: string): OADAClient; | ||
getToken(): string; | ||
getDomain(): string; | ||
disconnect(): Promise<void>; | ||
get(request: GETRequest): Promise<Response>; | ||
watch(request: WatchRequest): Promise<Response>; | ||
watch(request: WatchRequest): Promise<string>; | ||
unwatch(requestId: string): Promise<Response>; | ||
private _recursiveGet; | ||
put(request: PUTRequest): Promise<Response>; | ||
post(request: POSTRequest): Promise<Response>; | ||
head(request: HEADRequest): Promise<Response>; | ||
@@ -44,0 +57,0 @@ delete(request: DELETERequest): Promise<Response>; |
@@ -17,30 +17,37 @@ "use strict"; | ||
class OADAClient { | ||
constructor() { | ||
constructor(config) { | ||
this._token = ""; | ||
this._domain = ""; | ||
this._concurrency = 1; | ||
this._domain = config.domain; | ||
this._token = config.token || this._token; | ||
this._concurrency = config.concurrency || this._concurrency; | ||
this._ws = new websocket_1.WebSocketClient(this._domain, this._concurrency); | ||
} | ||
connect(config) { | ||
if (this._ws && this._ws.isConnected()) { | ||
throw new Error("Already connected"); | ||
} | ||
if (!config.token) { | ||
throw new Error("Token is required."); | ||
} | ||
this._ws = new websocket_1.WebSocketClient(config.domain); | ||
this._token = config.token; | ||
return this._ws.connect(); | ||
clone(token) { | ||
const c = new OADAClient({ | ||
domain: this._domain, | ||
token: token, | ||
concurrency: this._concurrency, | ||
_ws: this._ws, | ||
}); | ||
return c; | ||
} | ||
getToken() { | ||
return this._token; | ||
} | ||
getDomain() { | ||
return this._domain; | ||
} | ||
disconnect() { | ||
if (!this._ws || !this._ws.isConnected()) { | ||
if (!this._ws.isConnected()) { | ||
throw new Error("Not connected"); | ||
} | ||
this._ws.disconnect(); | ||
return this._ws.disconnect(); | ||
} | ||
async get(request) { | ||
if (!this._ws || !this._ws.isConnected()) { | ||
throw new Error("Not connected."); | ||
} | ||
const topLevelResponse = await this._ws.request({ | ||
method: "get", | ||
headers: { | ||
authorization: "Bearer " + this._token, | ||
authorization: `Bearer ${this._token}`, | ||
}, | ||
@@ -52,7 +59,11 @@ path: request.path, | ||
const subTree = utils.getObjectAtPath(request.tree, arrayPath); | ||
topLevelResponse.data = await this._recursiveGet(request.path, subTree, topLevelResponse.data); | ||
topLevelResponse.data = await this._recursiveGet(request.path, subTree, topLevelResponse.data || {}); | ||
} | ||
if (request.watchCallback) { | ||
const watchResponse = await this.watch({ | ||
const rev = topLevelResponse.headers | ||
? topLevelResponse.headers["x-oada-rev"] | ||
: undefined; | ||
await this.watch({ | ||
path: request.path, | ||
rev, | ||
watchCallback: request.watchCallback, | ||
@@ -64,19 +75,29 @@ }); | ||
async watch(request) { | ||
if (!this._ws || !this._ws.isConnected()) { | ||
throw new Error("Not connected."); | ||
let headers = {}; | ||
if (request.rev) { | ||
headers["x-oada-rev"] = request.rev; | ||
} | ||
const callback = (response) => { | ||
if (!response.change) { | ||
return; | ||
const r = await this._ws.request({ | ||
method: "watch", | ||
headers: Object.assign({ authorization: `Bearer ${this._token}` }, headers), | ||
path: request.path, | ||
}, (resp) => { | ||
for (const change of resp.change) { | ||
request.watchCallback(change); | ||
} | ||
request.watchCallback(response); | ||
}; | ||
const wsReq = { | ||
method: "watch", | ||
}); | ||
if (r.status !== 200) { | ||
throw new Error("Watch request failed!"); | ||
} | ||
return r.requestId[0]; | ||
} | ||
async unwatch(requestId) { | ||
return await this._ws.request({ | ||
path: "", | ||
headers: { | ||
authorization: "Bearer " + this._token, | ||
authorization: "", | ||
}, | ||
path: request.path, | ||
}; | ||
return await this._ws.request(wsReq, callback); | ||
method: "unwatch", | ||
requestId: requestId, | ||
}); | ||
} | ||
@@ -92,4 +113,4 @@ async _recursiveGet(path, subTree, data) { | ||
if (subTree["*"]) { | ||
children = Object.keys(data || {}).reduce((acc, key) => { | ||
if (typeof data[key] == "object") { | ||
children = Object.keys(data).reduce((acc, key) => { | ||
if (data && typeof data[key] == "object") { | ||
acc.push({ treeKey: "*", dataKey: key }); | ||
@@ -102,3 +123,3 @@ } | ||
children = Object.keys(subTree || {}).reduce((acc, key) => { | ||
if (typeof data[key] == "object") { | ||
if (data && typeof data[key] == "object") { | ||
acc.push({ treeKey: key, dataKey: key }); | ||
@@ -111,2 +132,5 @@ } | ||
const childPath = path + "/" + item.dataKey; | ||
if (!data) { | ||
return; | ||
} | ||
const res = await this._recursiveGet(childPath, subTree[item.treeKey], data[item.dataKey]); | ||
@@ -121,8 +145,5 @@ data[item.dataKey] = res; | ||
async put(request) { | ||
if (!this._ws || !this._ws.isConnected()) { | ||
throw new Error("Not connected."); | ||
} | ||
const pathArray = utils.toArrayPath(request.path); | ||
if (request.tree) { | ||
let linkObj; | ||
let linkObj = null; | ||
let newResourcePathArray = []; | ||
@@ -161,13 +182,10 @@ for (let i = pathArray.length - 1; i >= 0; i--) { | ||
let contentType = request.contentType || | ||
request.data["_type"] || | ||
(request.data && request.data["_type"]) || | ||
(request.tree | ||
? utils.getObjectAtPath(request.tree, pathArray)["_type"] | ||
: undefined); | ||
if (!contentType) { | ||
throw new Error("Content type is not specified."); | ||
} | ||
: "application/json"); | ||
return this._ws.request({ | ||
method: "put", | ||
headers: { | ||
authorization: "Bearer " + this._token, | ||
authorization: `Bearer ${this._token}`, | ||
"content-type": contentType, | ||
@@ -179,10 +197,29 @@ }, | ||
} | ||
async head(request) { | ||
if (!this._ws || !this._ws.isConnected()) { | ||
throw new Error("Not connected."); | ||
async post(request) { | ||
const pathArray = utils.toArrayPath(request.path); | ||
const data = request.data; | ||
if (request.tree) { | ||
request.data = {}; | ||
await this.put(request); | ||
} | ||
let contentType = request.contentType || | ||
(request.data && request.data["_type"]) || | ||
(request.tree | ||
? utils.getObjectAtPath(request.tree, pathArray)["_type"] | ||
: "application/json"); | ||
return this._ws.request({ | ||
method: "post", | ||
headers: { | ||
authorization: `Bearer ${this._token}`, | ||
"content-type": contentType, | ||
}, | ||
path: request.path, | ||
data, | ||
}); | ||
} | ||
async head(request) { | ||
return this._ws.request({ | ||
method: "head", | ||
headers: { | ||
authorization: "Bearer " + this._token, | ||
authorization: `Bearer ${this._token}`, | ||
}, | ||
@@ -193,9 +230,6 @@ path: request.path, | ||
async delete(request) { | ||
if (!this._ws || !this._ws.isConnected()) { | ||
throw new Error("Not connected."); | ||
} | ||
return this._ws.request({ | ||
method: "delete", | ||
headers: { | ||
authorization: "Bearer " + this._token, | ||
authorization: `Bearer ${this._token}`, | ||
}, | ||
@@ -207,6 +241,5 @@ path: request.path, | ||
const resourceId = "resources/" + ksuid_1.default.randomSync().string; | ||
const fullData = Object.assign({ _id: resourceId, _type: contentType }, data); | ||
const putResponse = await this.put({ | ||
await this.put({ | ||
path: "/" + resourceId, | ||
data: fullData, | ||
data, | ||
contentType, | ||
@@ -217,3 +250,5 @@ }); | ||
async _resourceExists(path) { | ||
const headResponse = await this.head({ path }).catch((msg) => { | ||
const headResponse = await this.head({ | ||
path, | ||
}).catch((msg) => { | ||
if (msg.status == 404) { | ||
@@ -223,3 +258,3 @@ return msg; | ||
else { | ||
throw new Error("Error"); | ||
throw new Error(`Error: ${msg.statusText}`); | ||
} | ||
@@ -226,0 +261,0 @@ }); |
import { OADAClient, Config } from "./client"; | ||
export declare function createInstance(): OADAClient; | ||
export declare function createInstance(config: Config): OADAClient; | ||
export declare function connect(config: Config): Promise<OADAClient>; | ||
export { OADAClient, Config, GETRequest, PUTRequest, HEADRequest, WatchRequest } from "./client"; | ||
export { OADAClient, Config, GETRequest, PUTRequest, HEADRequest, WatchRequest, } from "./client"; | ||
export declare type Json = null | boolean | number | string | Json[] | { | ||
[prop: string]: Json; | ||
}; | ||
export declare type JsonCompatible<T> = { | ||
[P in keyof T]: T[P] extends Json ? T[P] : Pick<T, P> extends Required<Pick<T, P>> ? never : T[P] extends (() => unknown) | undefined ? never : JsonCompatible<T[P]>; | ||
}; | ||
export interface Change { | ||
type: "merge" | "delete"; | ||
body: Json; | ||
path: string; | ||
resource_id: string; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const client_1 = require("./client"); | ||
function createInstance() { | ||
return new client_1.OADAClient(); | ||
function createInstance(config) { | ||
return new client_1.OADAClient(config); | ||
} | ||
exports.createInstance = createInstance; | ||
async function connect(config) { | ||
const instance = createInstance(); | ||
await instance.connect(config); | ||
return Promise.resolve(instance); | ||
return new client_1.OADAClient(config); | ||
} | ||
@@ -13,0 +11,0 @@ exports.connect = connect; |
@@ -1,15 +0,22 @@ | ||
export interface Request { | ||
method: string; | ||
headers: { | ||
[key: string]: string; | ||
}; | ||
import { Json, Change } from "."; | ||
export interface SocketRequest { | ||
requestId?: string; | ||
path: string; | ||
data?: object; | ||
requestId?: string; | ||
method: "head" | "get" | "put" | "post" | "delete" | "watch" | "unwatch"; | ||
headers: Record<string, string>; | ||
data?: Json; | ||
} | ||
export interface Response { | ||
headers: object; | ||
export interface SocketResponse { | ||
requestId: string | Array<string>; | ||
status: number; | ||
data: object; | ||
statusText: string; | ||
headers: Record<string, string>; | ||
data: Json; | ||
} | ||
export interface SocketChange { | ||
requestId: Array<string>; | ||
resourceId: string; | ||
path_leftover: string | Array<string>; | ||
change: Array<Change>; | ||
} | ||
export declare class WebSocketClient { | ||
@@ -20,8 +27,9 @@ private _ws; | ||
private _requests; | ||
constructor(domain: string); | ||
connect(): Promise<void>; | ||
disconnect(): void; | ||
private _q; | ||
constructor(domain: string, concurrency?: number); | ||
disconnect(): Promise<void>; | ||
isConnected(): boolean; | ||
request(req: Request, callback?: (response: Response) => void): Promise<Response>; | ||
request(req: SocketRequest, callback?: (response: Readonly<SocketChange>) => void): Promise<SocketResponse>; | ||
private doRequest; | ||
private _receive; | ||
} |
"use strict"; | ||
var __rest = (this && this.__rest) || function (s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -8,31 +19,38 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
const ksuid_1 = __importDefault(require("ksuid")); | ||
const p_queue_1 = __importDefault(require("p-queue")); | ||
const debug_1 = __importDefault(require("debug")); | ||
const trace = debug_1.default("@oada/client:ws:trace"); | ||
const error = debug_1.default("@oada/client:ws:error"); | ||
const request_1 = require("@oada/types/oada/websockets/request"); | ||
const response_1 = require("@oada/types/oada/websockets/response"); | ||
const change_1 = require("@oada/types/oada/websockets/change"); | ||
const v2_1 = require("@oada/types/oada/change/v2"); | ||
class WebSocketClient { | ||
constructor(domain) { | ||
constructor(domain, concurrency = 10) { | ||
this._connected = false; | ||
this._domain = domain; | ||
this._requests = new Map(); | ||
} | ||
connect() { | ||
if (this._connected) { | ||
throw new Error("Already connected to server."); | ||
} | ||
return new Promise((resolve, reject) => { | ||
this._ws = new WebSocket("wss://" + this._domain, { | ||
this._ws = new Promise((resolve) => { | ||
const ws = new WebSocket("wss://" + this._domain, { | ||
origin: "https://" + this._domain, | ||
}); | ||
this._ws.onopen = (e) => { | ||
ws.onopen = () => { | ||
this._connected = true; | ||
resolve(); | ||
resolve(ws); | ||
}; | ||
this._ws.onclose = () => { | ||
ws.onclose = () => { | ||
this._connected = false; | ||
}; | ||
this._ws.onmessage = this._receive.bind(this); | ||
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}`); | ||
}); | ||
} | ||
disconnect() { | ||
async disconnect() { | ||
if (!this._connected) { | ||
return; | ||
} | ||
this._ws.close(); | ||
(await this._ws).close(); | ||
} | ||
@@ -43,11 +61,9 @@ isConnected() { | ||
request(req, callback) { | ||
if (!this._connected) { | ||
throw new Error("Not connected to server."); | ||
} | ||
if (req.requestId) { | ||
throw new Error("Request ID exists."); | ||
} | ||
const requestId = ksuid_1.default.randomSync().string; | ||
return this._q.add(() => this.doRequest(req, callback)); | ||
} | ||
async doRequest(req, callback) { | ||
const requestId = req.requestId || ksuid_1.default.randomSync().string; | ||
req.requestId = requestId; | ||
this._ws.send(JSON.stringify(req)); | ||
request_1.assert(req); | ||
(await this._ws).send(JSON.stringify(req)); | ||
return new Promise((resolve, reject) => { | ||
@@ -63,33 +79,55 @@ this._requests.set(requestId, { | ||
} | ||
_receive(e) { | ||
let msg = JSON.parse(e.data); | ||
if (!msg.requestId) { | ||
return; | ||
} | ||
let request = this._requests.get(msg.requestId); | ||
if (request) { | ||
if (!request.settled) { | ||
request.settled = true; | ||
if (msg.status && msg.status == "success") { | ||
msg.status = 200; | ||
_receive(m) { | ||
try { | ||
const msg = JSON.parse(m.data.toString()); | ||
let requestIds; | ||
if (Array.isArray(msg.requestId)) { | ||
requestIds = msg.requestId; | ||
} | ||
else { | ||
requestIds = [msg.requestId]; | ||
} | ||
for (const requestId of requestIds) { | ||
let request = this._requests.get(requestId); | ||
if (request) { | ||
if (response_1.is(msg)) { | ||
if (!request.callback) { | ||
this._requests.delete(requestId); | ||
} | ||
if (!request.settled) { | ||
request.settled = true; | ||
if (msg.status && msg.status >= 200 && msg.status < 300) { | ||
request.resolve(msg); | ||
} | ||
else if (msg.status) { | ||
request.reject(msg); | ||
} | ||
else { | ||
throw new Error("Request failed"); | ||
} | ||
} | ||
} | ||
else if (request.callback && change_1.is(msg)) { | ||
v2_1.assert(msg.change); | ||
const m = { | ||
requestId: msg.requestId, | ||
resourceId: msg.resourceId, | ||
path_leftover: msg.path_leftover, | ||
change: msg.change.map((_a) => { | ||
var { body } = _a, rest = __rest(_a, ["body"]); | ||
return Object.assign(Object.assign({}, rest), { body: body }); | ||
}), | ||
}; | ||
request.callback(m); | ||
} | ||
else { | ||
throw new Error("Invalid websocket payload received"); | ||
} | ||
} | ||
if (msg.status && msg.status >= 200 && msg.status < 300) { | ||
const response = { | ||
headers: msg.headers, | ||
status: msg.status, | ||
data: msg.data, | ||
}; | ||
request.resolve(response); | ||
} | ||
else if (msg.status) { | ||
request.reject(msg); | ||
} | ||
else { | ||
throw new Error("Request failed"); | ||
} | ||
} | ||
if (request.callback) { | ||
request.callback(msg); | ||
} | ||
} | ||
catch (e) { | ||
error(`[Websocket ${this._domain}] Received invalid response. Ignoring.`); | ||
trace(`[Websocket ${this._domain}] Received invalid response. %O`, e); | ||
} | ||
} | ||
@@ -96,0 +134,0 @@ } |
{ | ||
"name": "@oada/client", | ||
"version": "0.0.3", | ||
"version": "1.0.0", | ||
"description": "A lightweight client tool to interact with an OADA-compliant server", | ||
@@ -18,6 +18,9 @@ "main": "dist/index.js", | ||
"author": "", | ||
"license": "ISC", | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"@oada/types": "^1.0.6", | ||
"debug": "^4.1.1", | ||
"isomorphic-ws": "^4.0.1", | ||
"ksuid": "^1.2.0", | ||
"p-queue": "^6.4.0", | ||
"ws": "^7.2.3" | ||
@@ -27,5 +30,7 @@ }, | ||
"@types/chai": "^4.2.11", | ||
"@types/debug": "^4.1.5", | ||
"@types/mocha": "^7.0.2", | ||
"@types/node": "^13.13.4", | ||
"@types/uuid": "^7.0.2", | ||
"@types/ws": "^7.2.4", | ||
"chai": "^4.2.0", | ||
@@ -32,0 +37,0 @@ "mocha": "^7.1.1", |
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
36892
588
1
6
10
+ Added@oada/types@^1.0.6
+ Addeddebug@^4.1.1
+ Addedp-queue@^6.4.0
+ Added@oada/types@1.8.1(transitive)
+ Addedajv@8.17.1(transitive)
+ Addedajv-formats@2.1.1(transitive)
+ Addedajv-formats-draft2019@1.6.1(transitive)
+ Addedcommander@2.20.3(transitive)
+ Addeddebug@4.3.7(transitive)
+ Addeddiscontinuous-range@1.0.0(transitive)
+ Addedeventemitter3@4.0.7(transitive)
+ Addedextend@3.0.2(transitive)
+ Addedfast-deep-equal@3.1.3(transitive)
+ Addedfast-uri@3.0.3(transitive)
+ Addedjson-schema-traverse@1.0.0(transitive)
+ Addedmoo@0.5.2(transitive)
+ Addedms@2.1.3(transitive)
+ Addednearley@2.20.1(transitive)
+ Addedp-finally@1.0.0(transitive)
+ Addedp-queue@6.6.2(transitive)
+ Addedp-timeout@3.2.0(transitive)
+ Addedpunycode@2.3.1(transitive)
+ Addedrailroad-diagrams@1.0.0(transitive)
+ Addedrandexp@0.4.6(transitive)
+ Addedrequire-from-string@2.0.2(transitive)
+ Addedret@0.1.15(transitive)
+ Addedschemes@1.4.0(transitive)
+ Addedsmtp-address-parser@1.1.0(transitive)
+ Addeduri-js@4.4.1(transitive)