graphql-subscriptions-client
Advanced tools
Comparing version 0.15.0 to 0.16.0
@@ -5,7 +5,7 @@ // src/index.ts | ||
import $$observable from "symbol-observable"; | ||
const WS_MINTIMEOUT = 1e3; | ||
const WS_TIMEOUT = 3e4; | ||
const isString = (value) => typeof value === "string"; | ||
const isObject = (value) => value !== null && typeof value === "object"; | ||
class SubscriptionClient { | ||
var WS_MIN_TIMEOUT = 1e3; | ||
var WS_TIMEOUT = 3e4; | ||
var isString = (value) => typeof value === "string"; | ||
var isObject = (value) => value !== null && typeof value === "object"; | ||
var SubscriptionClient = class { | ||
constructor(url, options) { | ||
@@ -15,3 +15,3 @@ const { | ||
connectionParams = {}, | ||
minTimeout = WS_MINTIMEOUT, | ||
minTimeout = WS_MIN_TIMEOUT, | ||
timeout = WS_TIMEOUT, | ||
@@ -355,22 +355,27 @@ reconnect = false, | ||
this.client.addEventListener("message", ({data}) => { | ||
this.processReceivedData(data); | ||
let parsedMessage; | ||
try { | ||
parsedMessage = JSON.parse(data); | ||
} catch (error) { | ||
throw new Error(`Message must be JSON-parseable. Got: ${data}`); | ||
} | ||
if (Array.isArray(parsedMessage)) { | ||
for (const message of parsedMessage) { | ||
this.processReceivedMessage(message); | ||
} | ||
} else { | ||
this.processReceivedMessage(parsedMessage); | ||
} | ||
}); | ||
} | ||
processReceivedData(receivedData) { | ||
let parsedMessage; | ||
let opId; | ||
try { | ||
parsedMessage = JSON.parse(receivedData); | ||
opId = parsedMessage.id; | ||
} catch (error) { | ||
throw new Error(`Message must be JSON-parseable. Got: ${receivedData}`); | ||
} | ||
if (["data", "complete", "error"].includes(parsedMessage.type) && !this.operations[opId]) { | ||
processReceivedMessage(message) { | ||
const opId = message.id; | ||
if (["data", "complete", "error"].includes(message.type) && !this.operations[opId]) { | ||
this.unsubscribe(opId); | ||
return; | ||
} | ||
switch (parsedMessage.type) { | ||
switch (message.type) { | ||
case "connection_error": | ||
if (this.connectionCallback) { | ||
this.connectionCallback(parsedMessage.payload); | ||
this.connectionCallback(message.payload); | ||
} | ||
@@ -392,9 +397,9 @@ break; | ||
case "error": | ||
this.operations[opId].handler(this.formatErrors(parsedMessage.payload), null); | ||
this.operations[opId].handler(this.formatErrors(message.payload), null); | ||
delete this.operations[opId]; | ||
break; | ||
case "data": | ||
const parsedPayload = !parsedMessage.payload.errors ? parsedMessage.payload : { | ||
...parsedMessage.payload, | ||
errors: this.formatErrors(parsedMessage.payload.errors) | ||
const parsedPayload = !message.payload.errors ? message.payload : { | ||
...message.payload, | ||
errors: this.formatErrors(message.payload.errors) | ||
}; | ||
@@ -426,5 +431,5 @@ this.operations[opId].handler(null, parsedPayload); | ||
} | ||
} | ||
}; | ||
export { | ||
SubscriptionClient | ||
}; |
(() => { | ||
var __defineProperty = Object.defineProperty; | ||
var __hasOwnProperty = Object.prototype.hasOwnProperty; | ||
var __commonJS = (callback, module) => () => { | ||
if (!module) { | ||
module = {exports: {}}; | ||
callback(module.exports, module); | ||
} | ||
return module.exports; | ||
}; | ||
var __markAsModule = (target) => { | ||
return __defineProperty(target, "__esModule", {value: true}); | ||
}; | ||
var __export = (target, all) => { | ||
var __create = Object.create; | ||
var __defProp = Object.defineProperty; | ||
var __getProtoOf = Object.getPrototypeOf; | ||
var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
var __getOwnPropNames = Object.getOwnPropertyNames; | ||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true}); | ||
var __exportStar = (target, module, desc) => { | ||
__markAsModule(target); | ||
for (var name in all) | ||
__defineProperty(target, name, {get: all[name], enumerable: true}); | ||
}; | ||
var __exportStar = (target, module) => { | ||
__markAsModule(target); | ||
if (typeof module === "object" || typeof module === "function") { | ||
for (let key in module) | ||
if (!__hasOwnProperty.call(target, key) && key !== "default") | ||
__defineProperty(target, key, {get: () => module[key], enumerable: true}); | ||
if (module && typeof module === "object" || typeof module === "function") { | ||
for (let key of __getOwnPropNames(module)) | ||
if (!__hasOwnProp.call(target, key) && key !== "default") | ||
__defProp(target, key, {get: () => module[key], enumerable: !(desc = __getOwnPropDesc(module, key)) || desc.enumerable}); | ||
} | ||
@@ -31,433 +21,432 @@ return target; | ||
return module; | ||
return __exportStar(__defineProperty({}, "default", {value: module, enumerable: true}), module); | ||
return __exportStar(__defProp(module != null ? __create(__getProtoOf(module)) : {}, "default", {value: module, enumerable: true}), module); | ||
}; | ||
// src/index.ts | ||
var require_src = __commonJS((exports) => { | ||
__export(exports, { | ||
SubscriptionClient: () => SubscriptionClient | ||
}); | ||
const backo2 = __toModule(require("backo2")); | ||
const eventemitter3 = __toModule(require("eventemitter3")); | ||
const symbol_observable = __toModule(require("symbol-observable")); | ||
const WS_MINTIMEOUT = 1e3; | ||
const WS_TIMEOUT = 3e4; | ||
const isString = (value) => typeof value === "string"; | ||
const isObject = (value) => value !== null && typeof value === "object"; | ||
class SubscriptionClient { | ||
constructor(url, options) { | ||
const { | ||
connectionCallback = void 0, | ||
connectionParams = {}, | ||
minTimeout = WS_MINTIMEOUT, | ||
timeout = WS_TIMEOUT, | ||
reconnect = false, | ||
reconnectionAttempts = Infinity, | ||
lazy = false, | ||
inactivityTimeout = 0 | ||
} = options || {}; | ||
this.wsImpl = WebSocket; | ||
this.connectionCallback = connectionCallback; | ||
this.url = url; | ||
this.operations = {}; | ||
this.nextOperationId = 0; | ||
this.wsMinTimeout = minTimeout; | ||
this.wsTimeout = timeout; | ||
this.unsentMessagesQueue = []; | ||
this.reconnect = reconnect; | ||
this.reconnecting = false; | ||
this.reconnectionAttempts = reconnectionAttempts; | ||
this.lazy = !!lazy; | ||
this.inactivityTimeout = inactivityTimeout; | ||
this.closedByUser = false; | ||
this.backoff = new backo2.default({jitter: 0.5}); | ||
this.eventEmitter = new eventemitter3.default(); | ||
this.client = null; | ||
this.maxConnectTimeGenerator = this.createMaxConnectTimeGenerator(); | ||
this.connectionParams = this.getConnectionParams(connectionParams); | ||
if (!this.lazy) { | ||
this.connect(); | ||
} | ||
var import_backo2 = __toModule(require("backo2")); | ||
var import_eventemitter3 = __toModule(require("eventemitter3")); | ||
var import_symbol_observable = __toModule(require("symbol-observable")); | ||
var WS_MIN_TIMEOUT = 1e3; | ||
var WS_TIMEOUT = 3e4; | ||
var isString = (value) => typeof value === "string"; | ||
var isObject = (value) => value !== null && typeof value === "object"; | ||
var SubscriptionClient = class { | ||
constructor(url, options) { | ||
const { | ||
connectionCallback = void 0, | ||
connectionParams = {}, | ||
minTimeout = WS_MIN_TIMEOUT, | ||
timeout = WS_TIMEOUT, | ||
reconnect = false, | ||
reconnectionAttempts = Infinity, | ||
lazy = false, | ||
inactivityTimeout = 0 | ||
} = options || {}; | ||
this.wsImpl = WebSocket; | ||
this.connectionCallback = connectionCallback; | ||
this.url = url; | ||
this.operations = {}; | ||
this.nextOperationId = 0; | ||
this.wsMinTimeout = minTimeout; | ||
this.wsTimeout = timeout; | ||
this.unsentMessagesQueue = []; | ||
this.reconnect = reconnect; | ||
this.reconnecting = false; | ||
this.reconnectionAttempts = reconnectionAttempts; | ||
this.lazy = !!lazy; | ||
this.inactivityTimeout = inactivityTimeout; | ||
this.closedByUser = false; | ||
this.backoff = new import_backo2.default({jitter: 0.5}); | ||
this.eventEmitter = new import_eventemitter3.default(); | ||
this.client = null; | ||
this.maxConnectTimeGenerator = this.createMaxConnectTimeGenerator(); | ||
this.connectionParams = this.getConnectionParams(connectionParams); | ||
if (!this.lazy) { | ||
this.connect(); | ||
} | ||
get status() { | ||
if (this.client === null) { | ||
return this.wsImpl.CLOSED; | ||
} | ||
return this.client.readyState; | ||
} | ||
get status() { | ||
if (this.client === null) { | ||
return this.wsImpl.CLOSED; | ||
} | ||
close(isForced = true, closedByUser = true) { | ||
this.clearInactivityTimeout(); | ||
if (this.client !== null) { | ||
this.closedByUser = closedByUser; | ||
if (isForced) { | ||
this.clearCheckConnectionInterval(); | ||
this.clearMaxConnectTimeout(); | ||
this.clearTryReconnectTimeout(); | ||
this.unsubscribeAll(); | ||
this.sendMessage(void 0, "connection_terminate", null); | ||
} | ||
this.client.close(); | ||
this.client = null; | ||
this.eventEmitter.emit("disconnected"); | ||
if (!isForced) { | ||
this.tryReconnect(); | ||
} | ||
return this.client.readyState; | ||
} | ||
close(isForced = true, closedByUser = true) { | ||
this.clearInactivityTimeout(); | ||
if (this.client !== null) { | ||
this.closedByUser = closedByUser; | ||
if (isForced) { | ||
this.clearCheckConnectionInterval(); | ||
this.clearMaxConnectTimeout(); | ||
this.clearTryReconnectTimeout(); | ||
this.unsubscribeAll(); | ||
this.sendMessage(void 0, "connection_terminate", null); | ||
} | ||
this.client.close(); | ||
this.client = null; | ||
this.eventEmitter.emit("disconnected"); | ||
if (!isForced) { | ||
this.tryReconnect(); | ||
} | ||
} | ||
request(request) { | ||
const getObserver = this.getObserver.bind(this); | ||
const executeOperation = this.executeOperation.bind(this); | ||
const unsubscribe = this.unsubscribe.bind(this); | ||
let opId; | ||
this.clearInactivityTimeout(); | ||
return { | ||
[symbol_observable.default]() { | ||
return this; | ||
}, | ||
subscribe(observerOrNext, onError, onComplete) { | ||
const observer = getObserver(observerOrNext, onError, onComplete); | ||
opId = executeOperation(request, (error, result) => { | ||
if (error === null && result === null) { | ||
if (observer.complete) { | ||
observer.complete(); | ||
} | ||
} else if (error) { | ||
if (observer.error) { | ||
observer.error(error[0]); | ||
} | ||
} else { | ||
if (observer.next) { | ||
observer.next(result); | ||
} | ||
} | ||
request(request) { | ||
const getObserver = this.getObserver.bind(this); | ||
const executeOperation = this.executeOperation.bind(this); | ||
const unsubscribe = this.unsubscribe.bind(this); | ||
let opId; | ||
this.clearInactivityTimeout(); | ||
return { | ||
[import_symbol_observable.default]() { | ||
return this; | ||
}, | ||
subscribe(observerOrNext, onError, onComplete) { | ||
const observer = getObserver(observerOrNext, onError, onComplete); | ||
opId = executeOperation(request, (error, result) => { | ||
if (error === null && result === null) { | ||
if (observer.complete) { | ||
observer.complete(); | ||
} | ||
}); | ||
return { | ||
unsubscribe: () => { | ||
if (opId) { | ||
unsubscribe(opId); | ||
opId = null; | ||
} | ||
} else if (error) { | ||
if (observer.error) { | ||
observer.error(error[0]); | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
on(eventName, callback, context) { | ||
const handler = this.eventEmitter.on(eventName, callback, context); | ||
return () => { | ||
handler.off(eventName, callback, context); | ||
}; | ||
} | ||
onConnected(callback, context) { | ||
return this.on("connected", callback, context); | ||
} | ||
onConnecting(callback, context) { | ||
return this.on("connecting", callback, context); | ||
} | ||
onDisconnected(callback, context) { | ||
return this.on("disconnected", callback, context); | ||
} | ||
onReconnected(callback, context) { | ||
return this.on("reconnected", callback, context); | ||
} | ||
onReconnecting(callback, context) { | ||
return this.on("reconnecting", callback, context); | ||
} | ||
onError(callback, context) { | ||
return this.on("error", callback, context); | ||
} | ||
unsubscribeAll() { | ||
Object.keys(this.operations).forEach((subId) => { | ||
this.unsubscribe(subId); | ||
}); | ||
} | ||
getConnectionParams(connectionParams) { | ||
return () => { | ||
return new Promise((resolve, reject) => { | ||
if (typeof connectionParams === "function") { | ||
try { | ||
return resolve(connectionParams()); | ||
} catch (error) { | ||
return reject(error); | ||
} else { | ||
if (observer.next) { | ||
observer.next(result); | ||
} | ||
} | ||
resolve(connectionParams); | ||
}); | ||
}; | ||
} | ||
executeOperation(options, handler) { | ||
if (this.client === null) { | ||
this.connect(); | ||
} | ||
const opId = this.generateOperationId(); | ||
this.operations[opId] = {options, handler}; | ||
try { | ||
this.checkOperationOptions(options, handler); | ||
if (this.operations[opId]) { | ||
this.operations[opId] = {options, handler}; | ||
this.sendMessage(opId, "start", options); | ||
} | ||
} catch (error) { | ||
this.unsubscribe(opId); | ||
handler(this.formatErrors(error)); | ||
} | ||
return opId; | ||
} | ||
getObserver(observerOrNext, error, complete) { | ||
if (typeof observerOrNext === "function") { | ||
return { | ||
next: (value) => observerOrNext(value), | ||
error: (e) => error && error(e), | ||
complete: () => complete && complete() | ||
unsubscribe: () => { | ||
if (opId) { | ||
unsubscribe(opId); | ||
opId = null; | ||
} | ||
} | ||
}; | ||
} | ||
return observerOrNext; | ||
} | ||
createMaxConnectTimeGenerator() { | ||
const minValue = this.wsMinTimeout; | ||
const maxValue = this.wsTimeout; | ||
return new backo2.default({ | ||
min: minValue, | ||
max: maxValue, | ||
factor: 1.2 | ||
}; | ||
} | ||
on(eventName, callback, context) { | ||
const handler = this.eventEmitter.on(eventName, callback, context); | ||
return () => { | ||
handler.off(eventName, callback, context); | ||
}; | ||
} | ||
onConnected(callback, context) { | ||
return this.on("connected", callback, context); | ||
} | ||
onConnecting(callback, context) { | ||
return this.on("connecting", callback, context); | ||
} | ||
onDisconnected(callback, context) { | ||
return this.on("disconnected", callback, context); | ||
} | ||
onReconnected(callback, context) { | ||
return this.on("reconnected", callback, context); | ||
} | ||
onReconnecting(callback, context) { | ||
return this.on("reconnecting", callback, context); | ||
} | ||
onError(callback, context) { | ||
return this.on("error", callback, context); | ||
} | ||
unsubscribeAll() { | ||
Object.keys(this.operations).forEach((subId) => { | ||
this.unsubscribe(subId); | ||
}); | ||
} | ||
getConnectionParams(connectionParams) { | ||
return () => { | ||
return new Promise((resolve, reject) => { | ||
if (typeof connectionParams === "function") { | ||
try { | ||
return resolve(connectionParams()); | ||
} catch (error) { | ||
return reject(error); | ||
} | ||
} | ||
resolve(connectionParams); | ||
}); | ||
}; | ||
} | ||
executeOperation(options, handler) { | ||
if (this.client === null) { | ||
this.connect(); | ||
} | ||
clearCheckConnectionInterval() { | ||
if (this.checkConnectionIntervalId) { | ||
clearInterval(this.checkConnectionIntervalId); | ||
this.checkConnectionIntervalId = null; | ||
const opId = this.generateOperationId(); | ||
this.operations[opId] = {options, handler}; | ||
try { | ||
this.checkOperationOptions(options, handler); | ||
if (this.operations[opId]) { | ||
this.operations[opId] = {options, handler}; | ||
this.sendMessage(opId, "start", options); | ||
} | ||
} catch (error) { | ||
this.unsubscribe(opId); | ||
handler(this.formatErrors(error)); | ||
} | ||
clearMaxConnectTimeout() { | ||
if (this.maxConnectTimeoutId) { | ||
clearTimeout(this.maxConnectTimeoutId); | ||
this.maxConnectTimeoutId = null; | ||
} | ||
return opId; | ||
} | ||
getObserver(observerOrNext, error, complete) { | ||
if (typeof observerOrNext === "function") { | ||
return { | ||
next: (value) => observerOrNext(value), | ||
error: (e) => error && error(e), | ||
complete: () => complete && complete() | ||
}; | ||
} | ||
clearTryReconnectTimeout() { | ||
if (this.tryReconnectTimeoutId) { | ||
clearTimeout(this.tryReconnectTimeoutId); | ||
this.tryReconnectTimeoutId = null; | ||
} | ||
return observerOrNext; | ||
} | ||
createMaxConnectTimeGenerator() { | ||
const minValue = this.wsMinTimeout; | ||
const maxValue = this.wsTimeout; | ||
return new import_backo2.default({ | ||
min: minValue, | ||
max: maxValue, | ||
factor: 1.2 | ||
}); | ||
} | ||
clearCheckConnectionInterval() { | ||
if (this.checkConnectionIntervalId) { | ||
clearInterval(this.checkConnectionIntervalId); | ||
this.checkConnectionIntervalId = null; | ||
} | ||
clearInactivityTimeout() { | ||
if (this.inactivityTimeoutId) { | ||
clearTimeout(this.inactivityTimeoutId); | ||
this.inactivityTimeoutId = null; | ||
} | ||
} | ||
clearMaxConnectTimeout() { | ||
if (this.maxConnectTimeoutId) { | ||
clearTimeout(this.maxConnectTimeoutId); | ||
this.maxConnectTimeoutId = null; | ||
} | ||
setInactivityTimeout() { | ||
if (this.inactivityTimeout > 0 && Object.keys(this.operations).length === 0) { | ||
this.inactivityTimeoutId = setTimeout(() => { | ||
if (Object.keys(this.operations).length === 0) { | ||
this.close(); | ||
} | ||
}, this.inactivityTimeout); | ||
} | ||
} | ||
clearTryReconnectTimeout() { | ||
if (this.tryReconnectTimeoutId) { | ||
clearTimeout(this.tryReconnectTimeoutId); | ||
this.tryReconnectTimeoutId = null; | ||
} | ||
checkOperationOptions(options, handler) { | ||
const {query, variables, operationName} = options; | ||
if (!query) { | ||
throw new Error("Must provide a query."); | ||
} | ||
if (!handler) { | ||
throw new Error("Must provide an handler."); | ||
} | ||
if (!isString(query) || operationName && !isString(operationName) || variables && !isObject(variables)) { | ||
throw new Error("Incorrect option types. query must be a string,`operationName` must be a string, and `variables` must be an object."); | ||
} | ||
} | ||
clearInactivityTimeout() { | ||
if (this.inactivityTimeoutId) { | ||
clearTimeout(this.inactivityTimeoutId); | ||
this.inactivityTimeoutId = null; | ||
} | ||
buildMessage(id, type, payload) { | ||
const payloadToReturn = payload && payload.query ? Object.assign({}, payload, { | ||
query: payload.query | ||
}) : payload; | ||
return { | ||
id, | ||
type, | ||
payload: payloadToReturn | ||
}; | ||
} | ||
formatErrors(errors) { | ||
if (Array.isArray(errors)) { | ||
return errors; | ||
} | ||
if (errors && errors.errors) { | ||
return this.formatErrors(errors.errors); | ||
} | ||
if (errors && errors.message) { | ||
return [errors]; | ||
} | ||
return [ | ||
{ | ||
name: "FormatedError", | ||
message: "Unknown error", | ||
originalError: errors | ||
} | ||
setInactivityTimeout() { | ||
if (this.inactivityTimeout > 0 && Object.keys(this.operations).length === 0) { | ||
this.inactivityTimeoutId = setTimeout(() => { | ||
if (Object.keys(this.operations).length === 0) { | ||
this.close(); | ||
} | ||
]; | ||
}, this.inactivityTimeout); | ||
} | ||
sendMessage(id, type, payload) { | ||
this.sendMessageRaw(this.buildMessage(id, type, payload)); | ||
} | ||
checkOperationOptions(options, handler) { | ||
const {query, variables, operationName} = options; | ||
if (!query) { | ||
throw new Error("Must provide a query."); | ||
} | ||
sendMessageRaw(message) { | ||
switch (this.status) { | ||
case this.wsImpl.OPEN: | ||
const serializedMessage = JSON.stringify(message); | ||
try { | ||
JSON.parse(serializedMessage); | ||
} catch (error) { | ||
this.eventEmitter.emit("error", new Error(`Message must be JSON-serializable. Got: ${message}`)); | ||
} | ||
this.client.send(serializedMessage); | ||
break; | ||
case this.wsImpl.CONNECTING: | ||
this.unsentMessagesQueue.push(message); | ||
break; | ||
default: | ||
if (!this.reconnecting) { | ||
this.eventEmitter.emit("error", new Error("A message was not sent because socket is not connected, is closing or is already closed. Message was: " + JSON.stringify(message))); | ||
} | ||
} | ||
if (!handler) { | ||
throw new Error("Must provide an handler."); | ||
} | ||
generateOperationId() { | ||
return String(++this.nextOperationId); | ||
if (!isString(query) || operationName && !isString(operationName) || variables && !isObject(variables)) { | ||
throw new Error("Incorrect option types. query must be a string,`operationName` must be a string, and `variables` must be an object."); | ||
} | ||
tryReconnect() { | ||
if (!this.reconnect || this.backoff.attempts >= this.reconnectionAttempts) { | ||
return; | ||
} | ||
buildMessage(id, type, payload) { | ||
const payloadToReturn = payload && payload.query ? Object.assign({}, payload, { | ||
query: payload.query | ||
}) : payload; | ||
return { | ||
id, | ||
type, | ||
payload: payloadToReturn | ||
}; | ||
} | ||
formatErrors(errors) { | ||
if (Array.isArray(errors)) { | ||
return errors; | ||
} | ||
if (errors && errors.errors) { | ||
return this.formatErrors(errors.errors); | ||
} | ||
if (errors && errors.message) { | ||
return [errors]; | ||
} | ||
return [ | ||
{ | ||
name: "FormatedError", | ||
message: "Unknown error", | ||
originalError: errors | ||
} | ||
if (!this.reconnecting) { | ||
Object.keys(this.operations).forEach((key) => { | ||
this.unsentMessagesQueue.push(this.buildMessage(key, "start", this.operations[key].options)); | ||
}); | ||
this.reconnecting = true; | ||
} | ||
this.clearTryReconnectTimeout(); | ||
const delay = this.backoff.duration(); | ||
this.tryReconnectTimeoutId = setTimeout(() => { | ||
this.connect(); | ||
}, delay); | ||
]; | ||
} | ||
sendMessage(id, type, payload) { | ||
this.sendMessageRaw(this.buildMessage(id, type, payload)); | ||
} | ||
sendMessageRaw(message) { | ||
switch (this.status) { | ||
case this.wsImpl.OPEN: | ||
const serializedMessage = JSON.stringify(message); | ||
try { | ||
JSON.parse(serializedMessage); | ||
} catch (error) { | ||
this.eventEmitter.emit("error", new Error(`Message must be JSON-serializable. Got: ${message}`)); | ||
} | ||
this.client.send(serializedMessage); | ||
break; | ||
case this.wsImpl.CONNECTING: | ||
this.unsentMessagesQueue.push(message); | ||
break; | ||
default: | ||
if (!this.reconnecting) { | ||
this.eventEmitter.emit("error", new Error("A message was not sent because socket is not connected, is closing or is already closed. Message was: " + JSON.stringify(message))); | ||
} | ||
} | ||
flushUnsentMessagesQueue() { | ||
this.unsentMessagesQueue.forEach((message) => { | ||
this.sendMessageRaw(message); | ||
} | ||
generateOperationId() { | ||
return String(++this.nextOperationId); | ||
} | ||
tryReconnect() { | ||
if (!this.reconnect || this.backoff.attempts >= this.reconnectionAttempts) { | ||
return; | ||
} | ||
if (!this.reconnecting) { | ||
Object.keys(this.operations).forEach((key) => { | ||
this.unsentMessagesQueue.push(this.buildMessage(key, "start", this.operations[key].options)); | ||
}); | ||
this.unsentMessagesQueue = []; | ||
this.reconnecting = true; | ||
} | ||
checkConnection() { | ||
if (this.wasKeepAliveReceived) { | ||
this.wasKeepAliveReceived = false; | ||
return; | ||
} | ||
if (!this.reconnecting) { | ||
this.clearTryReconnectTimeout(); | ||
const delay = this.backoff.duration(); | ||
this.tryReconnectTimeoutId = setTimeout(() => { | ||
this.connect(); | ||
}, delay); | ||
} | ||
flushUnsentMessagesQueue() { | ||
this.unsentMessagesQueue.forEach((message) => { | ||
this.sendMessageRaw(message); | ||
}); | ||
this.unsentMessagesQueue = []; | ||
} | ||
checkConnection() { | ||
if (this.wasKeepAliveReceived) { | ||
this.wasKeepAliveReceived = false; | ||
return; | ||
} | ||
if (!this.reconnecting) { | ||
this.close(false, true); | ||
} | ||
} | ||
checkMaxConnectTimeout() { | ||
this.clearMaxConnectTimeout(); | ||
this.maxConnectTimeoutId = setTimeout(() => { | ||
if (this.status !== this.wsImpl.OPEN) { | ||
this.reconnecting = true; | ||
this.close(false, true); | ||
} | ||
} | ||
checkMaxConnectTimeout() { | ||
this.clearMaxConnectTimeout(); | ||
this.maxConnectTimeoutId = setTimeout(() => { | ||
if (this.status !== this.wsImpl.OPEN) { | ||
this.reconnecting = true; | ||
this.close(false, true); | ||
}, this.maxConnectTimeGenerator.duration()); | ||
} | ||
connect() { | ||
this.client = new WebSocket(this.url, "graphql-ws"); | ||
this.checkMaxConnectTimeout(); | ||
this.client.addEventListener("open", async () => { | ||
if (this.status === this.wsImpl.OPEN) { | ||
this.clearMaxConnectTimeout(); | ||
this.closedByUser = false; | ||
this.eventEmitter.emit(this.reconnecting ? "reconnecting" : "connecting"); | ||
try { | ||
const connectionParams = await this.connectionParams(); | ||
this.sendMessage(void 0, "connection_init", connectionParams); | ||
this.flushUnsentMessagesQueue(); | ||
} catch (error) { | ||
this.sendMessage(void 0, "connection_error", error); | ||
this.flushUnsentMessagesQueue(); | ||
} | ||
}, this.maxConnectTimeGenerator.duration()); | ||
} | ||
connect() { | ||
this.client = new WebSocket(this.url, "graphql-ws"); | ||
this.checkMaxConnectTimeout(); | ||
this.client.addEventListener("open", async () => { | ||
if (this.status === this.wsImpl.OPEN) { | ||
this.clearMaxConnectTimeout(); | ||
this.closedByUser = false; | ||
this.eventEmitter.emit(this.reconnecting ? "reconnecting" : "connecting"); | ||
try { | ||
const connectionParams = await this.connectionParams(); | ||
this.sendMessage(void 0, "connection_init", connectionParams); | ||
this.flushUnsentMessagesQueue(); | ||
} catch (error) { | ||
this.sendMessage(void 0, "connection_error", error); | ||
this.flushUnsentMessagesQueue(); | ||
} | ||
} | ||
}); | ||
this.client.onclose = () => { | ||
if (!this.closedByUser) { | ||
this.close(false, false); | ||
} | ||
}; | ||
this.client.addEventListener("error", (error) => { | ||
this.eventEmitter.emit("error", error); | ||
}); | ||
this.client.addEventListener("message", ({data}) => { | ||
this.processReceivedData(data); | ||
}); | ||
} | ||
processReceivedData(receivedData) { | ||
} | ||
}); | ||
this.client.onclose = () => { | ||
if (!this.closedByUser) { | ||
this.close(false, false); | ||
} | ||
}; | ||
this.client.addEventListener("error", (error) => { | ||
this.eventEmitter.emit("error", error); | ||
}); | ||
this.client.addEventListener("message", ({data}) => { | ||
let parsedMessage; | ||
let opId; | ||
try { | ||
parsedMessage = JSON.parse(receivedData); | ||
opId = parsedMessage.id; | ||
parsedMessage = JSON.parse(data); | ||
} catch (error) { | ||
throw new Error(`Message must be JSON-parseable. Got: ${receivedData}`); | ||
throw new Error(`Message must be JSON-parseable. Got: ${data}`); | ||
} | ||
if (["data", "complete", "error"].includes(parsedMessage.type) && !this.operations[opId]) { | ||
this.unsubscribe(opId); | ||
return; | ||
if (Array.isArray(parsedMessage)) { | ||
for (const message of parsedMessage) { | ||
this.processReceivedMessage(message); | ||
} | ||
} else { | ||
this.processReceivedMessage(parsedMessage); | ||
} | ||
switch (parsedMessage.type) { | ||
case "connection_error": | ||
if (this.connectionCallback) { | ||
this.connectionCallback(parsedMessage.payload); | ||
} | ||
break; | ||
case "connection_ack": | ||
this.eventEmitter.emit(this.reconnecting ? "reconnected" : "connected"); | ||
this.reconnecting = false; | ||
this.backoff.reset(); | ||
this.maxConnectTimeGenerator.reset(); | ||
if (this.connectionCallback) { | ||
this.connectionCallback(); | ||
} | ||
break; | ||
case "complete": | ||
this.operations[opId].handler(null, null); | ||
delete this.operations[opId]; | ||
break; | ||
case "error": | ||
this.operations[opId].handler(this.formatErrors(parsedMessage.payload), null); | ||
delete this.operations[opId]; | ||
break; | ||
case "data": | ||
const parsedPayload = !parsedMessage.payload.errors ? parsedMessage.payload : { | ||
...parsedMessage.payload, | ||
errors: this.formatErrors(parsedMessage.payload.errors) | ||
}; | ||
this.operations[opId].handler(null, parsedPayload); | ||
break; | ||
case "ka": | ||
const firstKA = typeof this.wasKeepAliveReceived === "undefined"; | ||
this.wasKeepAliveReceived = true; | ||
if (firstKA) { | ||
this.checkConnection(); | ||
} | ||
if (this.checkConnectionIntervalId) { | ||
clearInterval(this.checkConnectionIntervalId); | ||
this.checkConnection(); | ||
} | ||
this.checkConnectionIntervalId = setInterval(this.checkConnection.bind(this), this.wsTimeout); | ||
break; | ||
default: | ||
throw new Error("Invalid message type!"); | ||
} | ||
}); | ||
} | ||
processReceivedMessage(message) { | ||
const opId = message.id; | ||
if (["data", "complete", "error"].includes(message.type) && !this.operations[opId]) { | ||
this.unsubscribe(opId); | ||
return; | ||
} | ||
unsubscribe(opId) { | ||
if (this.operations[opId]) { | ||
switch (message.type) { | ||
case "connection_error": | ||
if (this.connectionCallback) { | ||
this.connectionCallback(message.payload); | ||
} | ||
break; | ||
case "connection_ack": | ||
this.eventEmitter.emit(this.reconnecting ? "reconnected" : "connected"); | ||
this.reconnecting = false; | ||
this.backoff.reset(); | ||
this.maxConnectTimeGenerator.reset(); | ||
if (this.connectionCallback) { | ||
this.connectionCallback(); | ||
} | ||
break; | ||
case "complete": | ||
this.operations[opId].handler(null, null); | ||
delete this.operations[opId]; | ||
this.setInactivityTimeout(); | ||
this.sendMessage(opId, "stop", void 0); | ||
} | ||
break; | ||
case "error": | ||
this.operations[opId].handler(this.formatErrors(message.payload), null); | ||
delete this.operations[opId]; | ||
break; | ||
case "data": | ||
const parsedPayload = !message.payload.errors ? message.payload : { | ||
...message.payload, | ||
errors: this.formatErrors(message.payload.errors) | ||
}; | ||
this.operations[opId].handler(null, parsedPayload); | ||
break; | ||
case "ka": | ||
const firstKA = typeof this.wasKeepAliveReceived === "undefined"; | ||
this.wasKeepAliveReceived = true; | ||
if (firstKA) { | ||
this.checkConnection(); | ||
} | ||
if (this.checkConnectionIntervalId) { | ||
clearInterval(this.checkConnectionIntervalId); | ||
this.checkConnection(); | ||
} | ||
this.checkConnectionIntervalId = setInterval(this.checkConnection.bind(this), this.wsTimeout); | ||
break; | ||
default: | ||
throw new Error("Invalid message type!"); | ||
} | ||
} | ||
}); | ||
require_src(); | ||
unsubscribe(opId) { | ||
if (this.operations[opId]) { | ||
delete this.operations[opId]; | ||
this.setInactivityTimeout(); | ||
this.sendMessage(opId, "stop", void 0); | ||
} | ||
} | ||
}; | ||
})(); |
@@ -38,14 +38,14 @@ import Backoff from 'backo2'; | ||
private wsImpl; | ||
private connectionCallback; | ||
private url; | ||
private operations; | ||
private readonly connectionCallback; | ||
private readonly url; | ||
private readonly operations; | ||
private nextOperationId; | ||
private wsMinTimeout; | ||
private wsTimeout; | ||
private readonly wsMinTimeout; | ||
private readonly wsTimeout; | ||
private unsentMessagesQueue; | ||
private reconnect; | ||
private readonly reconnect; | ||
private reconnecting; | ||
private reconnectionAttempts; | ||
private lazy; | ||
private inactivityTimeout; | ||
private readonly reconnectionAttempts; | ||
private readonly lazy; | ||
private readonly inactivityTimeout; | ||
private closedByUser; | ||
@@ -56,3 +56,3 @@ private backoff; | ||
private maxConnectTimeGenerator; | ||
private connectionParams; | ||
private readonly connectionParams; | ||
private checkConnectionIntervalId; | ||
@@ -63,3 +63,3 @@ private maxConnectTimeoutId; | ||
private wasKeepAliveReceived; | ||
constructor(url: string, options: ClientOptions); | ||
constructor(url: string, options?: ClientOptions); | ||
get status(): number; | ||
@@ -109,3 +109,3 @@ close(isForced?: boolean, closedByUser?: boolean): void; | ||
connect(): void; | ||
processReceivedData(receivedData: string): void; | ||
processReceivedMessage(message: any): void; | ||
unsubscribe(opId: string): void; | ||
@@ -112,0 +112,0 @@ } |
@@ -5,7 +5,7 @@ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }// src/index.ts | ||
var _symbolobservable = require('symbol-observable'); var _symbolobservable2 = _interopRequireDefault(_symbolobservable); | ||
const WS_MINTIMEOUT = 1e3; | ||
const WS_TIMEOUT = 3e4; | ||
const isString = (value) => typeof value === "string"; | ||
const isObject = (value) => value !== null && typeof value === "object"; | ||
class SubscriptionClient { | ||
var WS_MIN_TIMEOUT = 1e3; | ||
var WS_TIMEOUT = 3e4; | ||
var isString = (value) => typeof value === "string"; | ||
var isObject = (value) => value !== null && typeof value === "object"; | ||
var SubscriptionClient = class { | ||
constructor(url, options) { | ||
@@ -15,3 +15,3 @@ const { | ||
connectionParams = {}, | ||
minTimeout = WS_MINTIMEOUT, | ||
minTimeout = WS_MIN_TIMEOUT, | ||
timeout = WS_TIMEOUT, | ||
@@ -355,22 +355,27 @@ reconnect = false, | ||
this.client.addEventListener("message", ({data}) => { | ||
this.processReceivedData(data); | ||
let parsedMessage; | ||
try { | ||
parsedMessage = JSON.parse(data); | ||
} catch (error) { | ||
throw new Error(`Message must be JSON-parseable. Got: ${data}`); | ||
} | ||
if (Array.isArray(parsedMessage)) { | ||
for (const message of parsedMessage) { | ||
this.processReceivedMessage(message); | ||
} | ||
} else { | ||
this.processReceivedMessage(parsedMessage); | ||
} | ||
}); | ||
} | ||
processReceivedData(receivedData) { | ||
let parsedMessage; | ||
let opId; | ||
try { | ||
parsedMessage = JSON.parse(receivedData); | ||
opId = parsedMessage.id; | ||
} catch (error) { | ||
throw new Error(`Message must be JSON-parseable. Got: ${receivedData}`); | ||
} | ||
if (["data", "complete", "error"].includes(parsedMessage.type) && !this.operations[opId]) { | ||
processReceivedMessage(message) { | ||
const opId = message.id; | ||
if (["data", "complete", "error"].includes(message.type) && !this.operations[opId]) { | ||
this.unsubscribe(opId); | ||
return; | ||
} | ||
switch (parsedMessage.type) { | ||
switch (message.type) { | ||
case "connection_error": | ||
if (this.connectionCallback) { | ||
this.connectionCallback(parsedMessage.payload); | ||
this.connectionCallback(message.payload); | ||
} | ||
@@ -392,9 +397,9 @@ break; | ||
case "error": | ||
this.operations[opId].handler(this.formatErrors(parsedMessage.payload), null); | ||
this.operations[opId].handler(this.formatErrors(message.payload), null); | ||
delete this.operations[opId]; | ||
break; | ||
case "data": | ||
const parsedPayload = !parsedMessage.payload.errors ? parsedMessage.payload : { | ||
...parsedMessage.payload, | ||
errors: this.formatErrors(parsedMessage.payload.errors) | ||
const parsedPayload = !message.payload.errors ? message.payload : { | ||
...message.payload, | ||
errors: this.formatErrors(message.payload.errors) | ||
}; | ||
@@ -426,5 +431,5 @@ this.operations[opId].handler(null, parsedPayload); | ||
} | ||
} | ||
}; | ||
exports.SubscriptionClient = SubscriptionClient; |
{ | ||
"name": "graphql-subscriptions-client", | ||
"version": "0.15.0", | ||
"version": "0.16.0", | ||
"description": "A simpler client for graphql subscriptions based on apollographql/subscriptions-transport-ws", | ||
@@ -13,3 +13,3 @@ "module": "dist/esm/index.js", | ||
"prepack": "npm run build", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"test": "jest --runInBand" | ||
}, | ||
@@ -31,11 +31,15 @@ "keywords": [ | ||
"eventemitter3": "^3.1.2", | ||
"symbol-observable": "^1.2.0", | ||
"ws": "^7.3.1" | ||
"symbol-observable": "^1.2.0" | ||
}, | ||
"devDependencies": { | ||
"@types/backo2": "^1.0.1", | ||
"@types/ws": "^7.2.7", | ||
"tsup": "^3.7.0", | ||
"typescript": "^4.0.3" | ||
"@types/jest": "^26.0.20", | ||
"@types/ws": "^7.4.0", | ||
"jest": "^26.6.3", | ||
"jest-websocket-mock": "^2.2.0", | ||
"mock-socket": "^9.0.3", | ||
"ts-jest": "^26.4.4", | ||
"tsup": "^3.11.0", | ||
"typescript": "^4.1.3" | ||
} | ||
} |
@@ -19,4 +19,11 @@ # graphql-subscriptions-client | ||
If you have a apollo-server instance you can use this for subscriptions only, pass all requests over the websocket. The API is similar to what's described at [subscriptions-transport-ws docs](https://github.com/apollographql/subscriptions-transport-ws#api-docs) except that it doesn't support middleware and requires queries to be strings. | ||
If you have a apollo-server instance you can use this for subscriptions only, pass all requests over the websocket. | ||
The API is similar to what's described at [subscriptions-transport-ws docs](https://github.com/apollographql/subscriptions-transport-ws#api-docs) except that it doesn't support middleware and requires queries to be strings. | ||
Also, this client supports batch messages as arrays from the server, and they will be processed as if they were received one after another, for example: | ||
``` | ||
[{ id: "1", type: "data", ... }, { id: "1", type: "complete" }] | ||
``` | ||
```js | ||
@@ -23,0 +30,0 @@ import { SubscriptionClient } from 'graphql-subscriptions-client'; |
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 tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
3
2
71
51464
9
1416