graphql-http-ws-client
Advanced tools
Comparing version 2.1.0 to 3.0.0
@@ -11,4 +11,7 @@ import { InMemoryCache } from '@apollo/client/cache/cache.cjs'; | ||
import { HttpLink } from '@apollo/client/link/http/http.cjs'; | ||
import { setContext } from '@apollo/client/link/context/context.cjs'; | ||
import { WebSocketLink } from '@apollo/client/link/ws/ws.cjs'; | ||
import { GraphQLWsLink } from '@apollo/client/link/subscriptions/subscriptions.cjs'; | ||
import { SubscriptionClient } from 'subscriptions-transport-ws'; | ||
import { createClient } from 'graphql-ws'; | ||
@@ -67,7 +70,7 @@ class ApolloClientWithGQL extends ApolloClient { | ||
var createGraphQLLinks = (graphQLURL, passedOptions) => { | ||
const options = { | ||
var createGraphQLLinks = (graphQLURL, options) => { | ||
const mergedOptions = { | ||
createHTTPLink: true, | ||
createWSLink: true, | ||
...passedOptions | ||
...options | ||
}; | ||
@@ -80,6 +83,11 @@ | ||
let httpLink = null; | ||
if(options.createHTTPLink) { | ||
if(mergedOptions.createHTTPLink) { | ||
const authLink = mergedOptions.httpLinkOptions?.setContext | ||
? setContext(mergedOptions.httpLinkOptions?.setContext) | ||
: undefined; | ||
const httpLinkOptions = { | ||
uri: graphQLURL, | ||
...options.httpLinkOptions | ||
...mergedOptions.httpLinkOptions | ||
}; | ||
@@ -90,3 +98,5 @@ | ||
httpLink = new HttpLink(httpLinkOptions); | ||
httpLink = authLink | ||
? authLink.concat(new HttpLink(httpLinkOptions)) // add auth link before http link | ||
: new HttpLink(httpLinkOptions); // no auth link | ||
} | ||
@@ -96,6 +106,35 @@ | ||
let subscriptionClient = null; | ||
if(options.createWSLink) { | ||
if(mergedOptions.createWSLink) { | ||
const wsSubprotocol = mergedOptions.wsSubprotocol | ||
? mergedOptions.wsSubprotocol.toLowerCase() | ||
: 'graphql-ws'; | ||
const wsLinkOptions = { | ||
reconnect: true, | ||
...options.wsLinkOptions | ||
...mergedOptions.wsLinkOptions, | ||
// override connectionParams to add subprotocol header | ||
connectionParams: async (...args) => { // add subprotocol | ||
console.log("CP"); | ||
const params = mergedOptions.wsLinkOptions?.connectionParams | ||
? await mergedOptions.wsLinkOptions?.connectionParams(...args) | ||
: {}; | ||
// merge in subprotocol headers | ||
console.log("SENDING", { | ||
...params, | ||
headers: { | ||
"sec-websocket-protocol": wsSubprotocol, | ||
...params.headers | ||
} | ||
}); | ||
return { | ||
...params, | ||
headers: { | ||
"sec-websocket-protocol": wsSubprotocol, | ||
...params.headers | ||
} | ||
} | ||
} | ||
}; | ||
@@ -106,7 +145,20 @@ | ||
const websocketImplementation = (typeof options.websocket === 'function') ? options.websocket : window.WebSocket; | ||
const websocketImplementation = typeof options.websocket === 'function' | ||
? options.websocket | ||
: window.WebSocket; | ||
subscriptionClient = new SubscriptionClient(httpURLToWS(graphQLURL), wsLinkOptions, websocketImplementation); | ||
wsLink = new WebSocketLink(subscriptionClient); | ||
if(wsSubprotocol === 'graphql-transport-ws') { // graphql-transport-ws, from graphql-ws | ||
subscriptionClient = createClient({ | ||
webSocketImpl: websocketImplementation, | ||
url: httpURLToWS(graphQLURL), | ||
...wsLinkOptions | ||
}); | ||
wsLink = new GraphQLWsLink(subscriptionClient); | ||
} | ||
else if(wsSubprotocol === 'graphql-ws') { // graphql-ws, from subscriptions-transport-ws | ||
subscriptionClient = new SubscriptionClient(httpURLToWS(graphQLURL), wsLinkOptions, websocketImplementation); | ||
wsLink = new WebSocketLink(subscriptionClient); | ||
} | ||
else | ||
throw new Error(`Unknown wsSubprotocol`); | ||
} | ||
@@ -121,3 +173,4 @@ | ||
wsLink, | ||
httpLink); | ||
httpLink | ||
); | ||
} | ||
@@ -138,3 +191,5 @@ else if(wsLink !== null) // only websocketLink exists | ||
return { | ||
link: options.hasOwnProperty('middleware') ? concat(options.middleware, link) : link, | ||
link: options.hasOwnProperty('middleware') | ||
? concat(options.middleware, link) | ||
: link, | ||
httpLink: httpLink, | ||
@@ -141,0 +196,0 @@ wsLink: wsLink, |
@@ -683,9 +683,543 @@ var __create = Object.create; | ||
import { HttpLink } from "@apollo/client/link/http/http.cjs"; | ||
import { setContext } from "@apollo/client/link/context/context.cjs"; | ||
import { WebSocketLink } from "@apollo/client/link/ws/ws.cjs"; | ||
import { GraphQLWsLink } from "@apollo/client/link/subscriptions/subscriptions.cjs"; | ||
import { SubscriptionClient } from "subscriptions-transport-ws"; | ||
var createGraphQLLinks_default = (graphQLURL, passedOptions) => { | ||
const options = __spreadValues({ | ||
// node_modules/graphql-ws/lib/client.mjs | ||
init_esm_shims(); | ||
// node_modules/graphql-ws/lib/common.mjs | ||
init_esm_shims(); | ||
// node_modules/graphql-ws/lib/utils.mjs | ||
init_esm_shims(); | ||
function extendedTypeof(val) { | ||
if (val === null) { | ||
return "null"; | ||
} | ||
if (Array.isArray(val)) { | ||
return "array"; | ||
} | ||
return typeof val; | ||
} | ||
function isObject(val) { | ||
return extendedTypeof(val) === "object"; | ||
} | ||
function areGraphQLErrors(obj) { | ||
return Array.isArray(obj) && obj.length > 0 && obj.every((ob) => "message" in ob); | ||
} | ||
function limitCloseReason(reason, whenTooLong) { | ||
return reason.length < 124 ? reason : whenTooLong; | ||
} | ||
// node_modules/graphql-ws/lib/common.mjs | ||
var GRAPHQL_TRANSPORT_WS_PROTOCOL = "graphql-transport-ws"; | ||
var CloseCode; | ||
(function(CloseCode2) { | ||
CloseCode2[CloseCode2["InternalServerError"] = 4500] = "InternalServerError"; | ||
CloseCode2[CloseCode2["InternalClientError"] = 4005] = "InternalClientError"; | ||
CloseCode2[CloseCode2["BadRequest"] = 4400] = "BadRequest"; | ||
CloseCode2[CloseCode2["BadResponse"] = 4004] = "BadResponse"; | ||
CloseCode2[CloseCode2["Unauthorized"] = 4401] = "Unauthorized"; | ||
CloseCode2[CloseCode2["Forbidden"] = 4403] = "Forbidden"; | ||
CloseCode2[CloseCode2["SubprotocolNotAcceptable"] = 4406] = "SubprotocolNotAcceptable"; | ||
CloseCode2[CloseCode2["ConnectionInitialisationTimeout"] = 4408] = "ConnectionInitialisationTimeout"; | ||
CloseCode2[CloseCode2["ConnectionAcknowledgementTimeout"] = 4504] = "ConnectionAcknowledgementTimeout"; | ||
CloseCode2[CloseCode2["SubscriberAlreadyExists"] = 4409] = "SubscriberAlreadyExists"; | ||
CloseCode2[CloseCode2["TooManyInitialisationRequests"] = 4429] = "TooManyInitialisationRequests"; | ||
})(CloseCode || (CloseCode = {})); | ||
var MessageType; | ||
(function(MessageType2) { | ||
MessageType2["ConnectionInit"] = "connection_init"; | ||
MessageType2["ConnectionAck"] = "connection_ack"; | ||
MessageType2["Ping"] = "ping"; | ||
MessageType2["Pong"] = "pong"; | ||
MessageType2["Subscribe"] = "subscribe"; | ||
MessageType2["Next"] = "next"; | ||
MessageType2["Error"] = "error"; | ||
MessageType2["Complete"] = "complete"; | ||
})(MessageType || (MessageType = {})); | ||
function validateMessage(val) { | ||
if (!isObject(val)) { | ||
throw new Error(`Message is expected to be an object, but got ${extendedTypeof(val)}`); | ||
} | ||
if (!val.type) { | ||
throw new Error(`Message is missing the 'type' property`); | ||
} | ||
if (typeof val.type !== "string") { | ||
throw new Error(`Message is expects the 'type' property to be a string, but got ${extendedTypeof(val.type)}`); | ||
} | ||
switch (val.type) { | ||
case MessageType.ConnectionInit: | ||
case MessageType.ConnectionAck: | ||
case MessageType.Ping: | ||
case MessageType.Pong: { | ||
if ("payload" in val && !isObject(val.payload)) { | ||
throw new Error(`"${val.type}" message expects the 'payload' property to be an object or missing, but got "${val.payload}"`); | ||
} | ||
break; | ||
} | ||
case MessageType.Subscribe: { | ||
if (typeof val.id !== "string") { | ||
throw new Error(`"${val.type}" message expects the 'id' property to be a string, but got ${extendedTypeof(val.id)}`); | ||
} | ||
if (!val.id) { | ||
throw new Error(`"${val.type}" message requires a non-empty 'id' property`); | ||
} | ||
if (!isObject(val.payload)) { | ||
throw new Error(`"${val.type}" message expects the 'payload' property to be an object, but got ${extendedTypeof(val.payload)}`); | ||
} | ||
if (typeof val.payload.query !== "string") { | ||
throw new Error(`"${val.type}" message payload expects the 'query' property to be a string, but got ${extendedTypeof(val.payload.query)}`); | ||
} | ||
if (val.payload.variables != null && !isObject(val.payload.variables)) { | ||
throw new Error(`"${val.type}" message payload expects the 'variables' property to be a an object or nullish or missing, but got ${extendedTypeof(val.payload.variables)}`); | ||
} | ||
if (val.payload.operationName != null && extendedTypeof(val.payload.operationName) !== "string") { | ||
throw new Error(`"${val.type}" message payload expects the 'operationName' property to be a string or nullish or missing, but got ${extendedTypeof(val.payload.operationName)}`); | ||
} | ||
if (val.payload.extensions != null && !isObject(val.payload.extensions)) { | ||
throw new Error(`"${val.type}" message payload expects the 'extensions' property to be a an object or nullish or missing, but got ${extendedTypeof(val.payload.extensions)}`); | ||
} | ||
break; | ||
} | ||
case MessageType.Next: { | ||
if (typeof val.id !== "string") { | ||
throw new Error(`"${val.type}" message expects the 'id' property to be a string, but got ${extendedTypeof(val.id)}`); | ||
} | ||
if (!val.id) { | ||
throw new Error(`"${val.type}" message requires a non-empty 'id' property`); | ||
} | ||
if (!isObject(val.payload)) { | ||
throw new Error(`"${val.type}" message expects the 'payload' property to be an object, but got ${extendedTypeof(val.payload)}`); | ||
} | ||
break; | ||
} | ||
case MessageType.Error: { | ||
if (typeof val.id !== "string") { | ||
throw new Error(`"${val.type}" message expects the 'id' property to be a string, but got ${extendedTypeof(val.id)}`); | ||
} | ||
if (!val.id) { | ||
throw new Error(`"${val.type}" message requires a non-empty 'id' property`); | ||
} | ||
if (!areGraphQLErrors(val.payload)) { | ||
throw new Error(`"${val.type}" message expects the 'payload' property to be an array of GraphQL errors, but got ${JSON.stringify(val.payload)}`); | ||
} | ||
break; | ||
} | ||
case MessageType.Complete: { | ||
if (typeof val.id !== "string") { | ||
throw new Error(`"${val.type}" message expects the 'id' property to be a string, but got ${extendedTypeof(val.id)}`); | ||
} | ||
if (!val.id) { | ||
throw new Error(`"${val.type}" message requires a non-empty 'id' property`); | ||
} | ||
break; | ||
} | ||
default: | ||
throw new Error(`Invalid message 'type' property "${val.type}"`); | ||
} | ||
return val; | ||
} | ||
function parseMessage(data, reviver) { | ||
try { | ||
return validateMessage(data); | ||
} catch (_a) { | ||
if (typeof data !== "string") { | ||
throw new Error("Only strings are parsable messages"); | ||
} | ||
const message = JSON.parse(data, reviver); | ||
return validateMessage(message); | ||
} | ||
} | ||
function stringifyMessage(msg, replacer) { | ||
validateMessage(msg); | ||
return JSON.stringify(msg, replacer); | ||
} | ||
// node_modules/graphql-ws/lib/client.mjs | ||
function createClient(options) { | ||
const { | ||
url, | ||
connectionParams, | ||
lazy = true, | ||
onNonLazyError = console.error, | ||
lazyCloseTimeout = 0, | ||
keepAlive = 0, | ||
disablePong, | ||
connectionAckWaitTimeout = 0, | ||
retryAttempts = 5, | ||
retryWait = async function randomisedExponentialBackoff(retries2) { | ||
let retryDelay = 1e3; | ||
for (let i = 0; i < retries2; i++) { | ||
retryDelay *= 2; | ||
} | ||
await new Promise((resolve) => setTimeout(resolve, retryDelay + Math.floor(Math.random() * (3e3 - 300) + 300))); | ||
}, | ||
shouldRetry = isLikeCloseEvent, | ||
isFatalConnectionProblem, | ||
on, | ||
webSocketImpl, | ||
generateID = function generateUUID() { | ||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { | ||
const r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8; | ||
return v.toString(16); | ||
}); | ||
}, | ||
jsonMessageReplacer: replacer, | ||
jsonMessageReviver: reviver | ||
} = options; | ||
let ws; | ||
if (webSocketImpl) { | ||
if (!isWebSocket(webSocketImpl)) { | ||
throw new Error("Invalid WebSocket implementation provided"); | ||
} | ||
ws = webSocketImpl; | ||
} else if (typeof WebSocket !== "undefined") { | ||
ws = WebSocket; | ||
} else if (typeof global !== "undefined") { | ||
ws = global.WebSocket || global.MozWebSocket; | ||
} else if (typeof window !== "undefined") { | ||
ws = window.WebSocket || window.MozWebSocket; | ||
} | ||
if (!ws) | ||
throw new Error("WebSocket implementation missing; on Node you can `import WebSocket from 'ws';` and pass `webSocketImpl: WebSocket` to `createClient`"); | ||
const WebSocketImpl = ws; | ||
const emitter = (() => { | ||
const message = (() => { | ||
const listeners2 = {}; | ||
return { | ||
on(id, listener) { | ||
listeners2[id] = listener; | ||
return () => { | ||
delete listeners2[id]; | ||
}; | ||
}, | ||
emit(message2) { | ||
var _a; | ||
if ("id" in message2) | ||
(_a = listeners2[message2.id]) === null || _a === void 0 ? void 0 : _a.call(listeners2, message2); | ||
} | ||
}; | ||
})(); | ||
const listeners = { | ||
connecting: (on === null || on === void 0 ? void 0 : on.connecting) ? [on.connecting] : [], | ||
opened: (on === null || on === void 0 ? void 0 : on.opened) ? [on.opened] : [], | ||
connected: (on === null || on === void 0 ? void 0 : on.connected) ? [on.connected] : [], | ||
ping: (on === null || on === void 0 ? void 0 : on.ping) ? [on.ping] : [], | ||
pong: (on === null || on === void 0 ? void 0 : on.pong) ? [on.pong] : [], | ||
message: (on === null || on === void 0 ? void 0 : on.message) ? [message.emit, on.message] : [message.emit], | ||
closed: (on === null || on === void 0 ? void 0 : on.closed) ? [on.closed] : [], | ||
error: (on === null || on === void 0 ? void 0 : on.error) ? [on.error] : [] | ||
}; | ||
return { | ||
onMessage: message.on, | ||
on(event, listener) { | ||
const l = listeners[event]; | ||
l.push(listener); | ||
return () => { | ||
l.splice(l.indexOf(listener), 1); | ||
}; | ||
}, | ||
emit(event, ...args) { | ||
for (const listener of [...listeners[event]]) { | ||
listener(...args); | ||
} | ||
} | ||
}; | ||
})(); | ||
function errorOrClosed(cb) { | ||
const listening = [ | ||
emitter.on("error", (err) => { | ||
listening.forEach((unlisten) => unlisten()); | ||
cb(err); | ||
}), | ||
emitter.on("closed", (event) => { | ||
listening.forEach((unlisten) => unlisten()); | ||
cb(event); | ||
}) | ||
]; | ||
} | ||
let connecting, locks = 0, retrying = false, retries = 0, disposed = false; | ||
async function connect() { | ||
const [socket, throwOnClose] = await (connecting !== null && connecting !== void 0 ? connecting : connecting = new Promise((connected, denied) => (async () => { | ||
if (retrying) { | ||
await retryWait(retries); | ||
if (!locks) { | ||
connecting = void 0; | ||
return denied({ code: 1e3, reason: "All Subscriptions Gone" }); | ||
} | ||
retries++; | ||
} | ||
emitter.emit("connecting"); | ||
const socket2 = new WebSocketImpl(typeof url === "function" ? await url() : url, GRAPHQL_TRANSPORT_WS_PROTOCOL); | ||
let connectionAckTimeout, queuedPing; | ||
function enqueuePing() { | ||
if (isFinite(keepAlive) && keepAlive > 0) { | ||
clearTimeout(queuedPing); | ||
queuedPing = setTimeout(() => { | ||
if (socket2.readyState === WebSocketImpl.OPEN) { | ||
socket2.send(stringifyMessage({ type: MessageType.Ping })); | ||
emitter.emit("ping", false, void 0); | ||
} | ||
}, keepAlive); | ||
} | ||
} | ||
errorOrClosed((errOrEvent) => { | ||
connecting = void 0; | ||
clearTimeout(connectionAckTimeout); | ||
clearTimeout(queuedPing); | ||
denied(errOrEvent); | ||
if (isLikeCloseEvent(errOrEvent) && errOrEvent.code === 4499) { | ||
socket2.close(4499, "Terminated"); | ||
socket2.onerror = null; | ||
socket2.onclose = null; | ||
} | ||
}); | ||
socket2.onerror = (err) => emitter.emit("error", err); | ||
socket2.onclose = (event) => emitter.emit("closed", event); | ||
socket2.onopen = async () => { | ||
try { | ||
emitter.emit("opened", socket2); | ||
const payload = typeof connectionParams === "function" ? await connectionParams() : connectionParams; | ||
if (socket2.readyState !== WebSocketImpl.OPEN) | ||
return; | ||
socket2.send(stringifyMessage(payload ? { | ||
type: MessageType.ConnectionInit, | ||
payload | ||
} : { | ||
type: MessageType.ConnectionInit | ||
}, replacer)); | ||
if (isFinite(connectionAckWaitTimeout) && connectionAckWaitTimeout > 0) { | ||
connectionAckTimeout = setTimeout(() => { | ||
socket2.close(CloseCode.ConnectionAcknowledgementTimeout, "Connection acknowledgement timeout"); | ||
}, connectionAckWaitTimeout); | ||
} | ||
enqueuePing(); | ||
} catch (err) { | ||
emitter.emit("error", err); | ||
socket2.close(CloseCode.InternalClientError, limitCloseReason(err instanceof Error ? err.message : new Error(err).message, "Internal client error")); | ||
} | ||
}; | ||
let acknowledged = false; | ||
socket2.onmessage = ({ data }) => { | ||
try { | ||
const message = parseMessage(data, reviver); | ||
emitter.emit("message", message); | ||
if (message.type === "ping" || message.type === "pong") { | ||
emitter.emit(message.type, true, message.payload); | ||
if (message.type === "pong") { | ||
enqueuePing(); | ||
} else if (!disablePong) { | ||
socket2.send(stringifyMessage(message.payload ? { | ||
type: MessageType.Pong, | ||
payload: message.payload | ||
} : { | ||
type: MessageType.Pong | ||
})); | ||
emitter.emit("pong", false, message.payload); | ||
} | ||
return; | ||
} | ||
if (acknowledged) | ||
return; | ||
if (message.type !== MessageType.ConnectionAck) | ||
throw new Error(`First message cannot be of type ${message.type}`); | ||
clearTimeout(connectionAckTimeout); | ||
acknowledged = true; | ||
emitter.emit("connected", socket2, message.payload); | ||
retrying = false; | ||
retries = 0; | ||
connected([ | ||
socket2, | ||
new Promise((_, reject) => errorOrClosed(reject)) | ||
]); | ||
} catch (err) { | ||
socket2.onmessage = null; | ||
emitter.emit("error", err); | ||
socket2.close(CloseCode.BadResponse, limitCloseReason(err instanceof Error ? err.message : new Error(err).message, "Bad response")); | ||
} | ||
}; | ||
})())); | ||
if (socket.readyState === WebSocketImpl.CLOSING) | ||
await throwOnClose; | ||
let release = () => { | ||
}; | ||
const released = new Promise((resolve) => release = resolve); | ||
return [ | ||
socket, | ||
release, | ||
Promise.race([ | ||
released.then(() => { | ||
if (!locks) { | ||
const complete = () => socket.close(1e3, "Normal Closure"); | ||
if (isFinite(lazyCloseTimeout) && lazyCloseTimeout > 0) { | ||
setTimeout(() => { | ||
if (!locks && socket.readyState === WebSocketImpl.OPEN) | ||
complete(); | ||
}, lazyCloseTimeout); | ||
} else { | ||
complete(); | ||
} | ||
} | ||
}), | ||
throwOnClose | ||
]) | ||
]; | ||
} | ||
function shouldRetryConnectOrThrow(errOrCloseEvent) { | ||
if (isLikeCloseEvent(errOrCloseEvent) && (isFatalInternalCloseCode(errOrCloseEvent.code) || [ | ||
CloseCode.InternalServerError, | ||
CloseCode.InternalClientError, | ||
CloseCode.BadRequest, | ||
CloseCode.BadResponse, | ||
CloseCode.Unauthorized, | ||
CloseCode.SubprotocolNotAcceptable, | ||
CloseCode.SubscriberAlreadyExists, | ||
CloseCode.TooManyInitialisationRequests | ||
].includes(errOrCloseEvent.code))) | ||
throw errOrCloseEvent; | ||
if (disposed) | ||
return false; | ||
if (isLikeCloseEvent(errOrCloseEvent) && errOrCloseEvent.code === 1e3) | ||
return locks > 0; | ||
if (!retryAttempts || retries >= retryAttempts) | ||
throw errOrCloseEvent; | ||
if (!shouldRetry(errOrCloseEvent)) | ||
throw errOrCloseEvent; | ||
if (isFatalConnectionProblem === null || isFatalConnectionProblem === void 0 ? void 0 : isFatalConnectionProblem(errOrCloseEvent)) | ||
throw errOrCloseEvent; | ||
return retrying = true; | ||
} | ||
if (!lazy) { | ||
(async () => { | ||
locks++; | ||
for (; ; ) { | ||
try { | ||
const [, , throwOnClose] = await connect(); | ||
await throwOnClose; | ||
} catch (errOrCloseEvent) { | ||
try { | ||
if (!shouldRetryConnectOrThrow(errOrCloseEvent)) | ||
return; | ||
} catch (errOrCloseEvent2) { | ||
return onNonLazyError === null || onNonLazyError === void 0 ? void 0 : onNonLazyError(errOrCloseEvent2); | ||
} | ||
} | ||
} | ||
})(); | ||
} | ||
return { | ||
on: emitter.on, | ||
subscribe(payload, sink) { | ||
const id = generateID(); | ||
let done = false, errored = false, releaser = () => { | ||
locks--; | ||
done = true; | ||
}; | ||
(async () => { | ||
locks++; | ||
for (; ; ) { | ||
try { | ||
const [socket, release, waitForReleaseOrThrowOnClose] = await connect(); | ||
if (done) | ||
return release(); | ||
const unlisten = emitter.onMessage(id, (message) => { | ||
switch (message.type) { | ||
case MessageType.Next: { | ||
sink.next(message.payload); | ||
return; | ||
} | ||
case MessageType.Error: { | ||
errored = true, done = true; | ||
sink.error(message.payload); | ||
releaser(); | ||
return; | ||
} | ||
case MessageType.Complete: { | ||
done = true; | ||
releaser(); | ||
return; | ||
} | ||
} | ||
}); | ||
socket.send(stringifyMessage({ | ||
id, | ||
type: MessageType.Subscribe, | ||
payload | ||
}, replacer)); | ||
releaser = () => { | ||
if (!done && socket.readyState === WebSocketImpl.OPEN) | ||
socket.send(stringifyMessage({ | ||
id, | ||
type: MessageType.Complete | ||
}, replacer)); | ||
locks--; | ||
done = true; | ||
release(); | ||
}; | ||
await waitForReleaseOrThrowOnClose.finally(unlisten); | ||
return; | ||
} catch (errOrCloseEvent) { | ||
if (!shouldRetryConnectOrThrow(errOrCloseEvent)) | ||
return; | ||
} | ||
} | ||
})().then(() => { | ||
if (!errored) | ||
sink.complete(); | ||
}).catch((err) => { | ||
sink.error(err); | ||
}); | ||
return () => { | ||
if (!done) | ||
releaser(); | ||
}; | ||
}, | ||
async dispose() { | ||
disposed = true; | ||
if (connecting) { | ||
const [socket] = await connecting; | ||
socket.close(1e3, "Normal Closure"); | ||
} | ||
}, | ||
terminate() { | ||
if (connecting) { | ||
emitter.emit("closed", { | ||
code: 4499, | ||
reason: "Terminated", | ||
wasClean: false | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
function isLikeCloseEvent(val) { | ||
return isObject(val) && "code" in val && "reason" in val; | ||
} | ||
function isFatalInternalCloseCode(code) { | ||
if ([ | ||
1e3, | ||
1001, | ||
1006, | ||
1005, | ||
1012, | ||
1013, | ||
1013 | ||
].includes(code)) | ||
return false; | ||
return code >= 1e3 && code <= 1999; | ||
} | ||
function isWebSocket(val) { | ||
return typeof val === "function" && "constructor" in val && "CLOSED" in val && "CLOSING" in val && "CONNECTING" in val && "OPEN" in val; | ||
} | ||
// src/createGraphQLLinks.js | ||
var createGraphQLLinks_default = (graphQLURL, options) => { | ||
var _a, _b; | ||
const mergedOptions = __spreadValues({ | ||
createHTTPLink: true, | ||
createWSLink: true | ||
}, passedOptions); | ||
}, options); | ||
const httpURLToWS = (url) => { | ||
@@ -695,21 +1229,48 @@ return url.replace(/(http)(s)?:\/\//, "ws$2://"); | ||
let httpLink = null; | ||
if (options.createHTTPLink) { | ||
if (mergedOptions.createHTTPLink) { | ||
const authLink = ((_a = mergedOptions.httpLinkOptions) == null ? void 0 : _a.setContext) ? setContext((_b = mergedOptions.httpLinkOptions) == null ? void 0 : _b.setContext) : void 0; | ||
const httpLinkOptions = __spreadValues({ | ||
uri: graphQLURL | ||
}, options.httpLinkOptions); | ||
}, mergedOptions.httpLinkOptions); | ||
if (typeof httpLinkOptions.fetch !== "function" && (typeof window !== "object" || typeof window.fetch !== "function")) | ||
throw new Error(`Missing fetch implementation on window.fetch or options.httpLinkOptions.fetch`); | ||
httpLink = new HttpLink(httpLinkOptions); | ||
httpLink = authLink ? authLink.concat(new HttpLink(httpLinkOptions)) : new HttpLink(httpLinkOptions); | ||
} | ||
let wsLink = null; | ||
let subscriptionClient = null; | ||
if (options.createWSLink) { | ||
const wsLinkOptions = __spreadValues({ | ||
if (mergedOptions.createWSLink) { | ||
const wsSubprotocol = mergedOptions.wsSubprotocol ? mergedOptions.wsSubprotocol.toLowerCase() : "graphql-ws"; | ||
const wsLinkOptions = __spreadProps(__spreadValues({ | ||
reconnect: true | ||
}, options.wsLinkOptions); | ||
}, mergedOptions.wsLinkOptions), { | ||
connectionParams: async (...args) => { | ||
var _a2, _b2; | ||
console.log("CP"); | ||
const params = ((_a2 = mergedOptions.wsLinkOptions) == null ? void 0 : _a2.connectionParams) ? await ((_b2 = mergedOptions.wsLinkOptions) == null ? void 0 : _b2.connectionParams(...args)) : {}; | ||
console.log("SENDING", __spreadProps(__spreadValues({}, params), { | ||
headers: __spreadValues({ | ||
"sec-websocket-protocol": wsSubprotocol | ||
}, params.headers) | ||
})); | ||
return __spreadProps(__spreadValues({}, params), { | ||
headers: __spreadValues({ | ||
"sec-websocket-protocol": wsSubprotocol | ||
}, params.headers) | ||
}); | ||
} | ||
}); | ||
if (typeof options.websocket !== "function" && (typeof window !== "object" || typeof window.WebSocket !== "function")) | ||
throw new Error(`Missing websocket implementation on window.WebSocket or options.websocket`); | ||
const websocketImplementation = typeof options.websocket === "function" ? options.websocket : window.WebSocket; | ||
subscriptionClient = new SubscriptionClient(httpURLToWS(graphQLURL), wsLinkOptions, websocketImplementation); | ||
wsLink = new WebSocketLink(subscriptionClient); | ||
if (wsSubprotocol === "graphql-transport-ws") { | ||
subscriptionClient = createClient(__spreadValues({ | ||
webSocketImpl: websocketImplementation, | ||
url: httpURLToWS(graphQLURL) | ||
}, wsLinkOptions)); | ||
wsLink = new GraphQLWsLink(subscriptionClient); | ||
} else if (wsSubprotocol === "graphql-ws") { | ||
subscriptionClient = new SubscriptionClient(httpURLToWS(graphQLURL), wsLinkOptions, websocketImplementation); | ||
wsLink = new WebSocketLink(subscriptionClient); | ||
} else | ||
throw new Error(`Unknown wsSubprotocol`); | ||
} | ||
@@ -716,0 +1277,0 @@ let transportLink = null; |
{ | ||
"name": "graphql-http-ws-client", | ||
"version": "2.1.0", | ||
"version": "3.0.0", | ||
"private": false, | ||
@@ -34,3 +34,4 @@ "license": "MIT", | ||
"typescript": "^4.6.4", | ||
"ws": "^8.6.0" | ||
"ws": "^8.6.0", | ||
"graphql-ws": "^5.9.0" | ||
}, | ||
@@ -37,0 +38,0 @@ "peerDependencies": { |
## GraphQL client over HTTP/WS | ||
### Node.js with HTTP and WS links | ||
### Node.js with HTTP and WS links, and context | ||
@@ -8,7 +8,21 @@ import { createGraphQLClient } from "graphql-http-ws-client"; | ||
import fetch from "node-fetch"; | ||
let addContext = async () => { | ||
return { | ||
headers: { | ||
Authorization: `Bearer MYAUTHTOKEN` | ||
} | ||
} | ||
}; | ||
const { client } = createGraphQLClient("MY_GRAPHQL_URL", { | ||
websocket: WebSocket, | ||
httpLinkOptions: { | ||
fetch: fetch | ||
createWSLink: true, | ||
createHTTPLink: true, | ||
websocket: WebSocket, | ||
httpLinkOptions: { | ||
fetch: fetch, | ||
setContext: addContext | ||
}, | ||
wsLinkOptions: { | ||
connectionParams: addContext | ||
} | ||
@@ -39,3 +53,27 @@ }); | ||
}); | ||
### Node.js with WS link and [graphql-transport-ws subprotocol](https://github.com/enisdenjo/graphql-ws/issues/154) | ||
import { createGraphQLClient } from "graphql-http-ws-client"; | ||
import WebSocket from "ws"; | ||
import fetch from "node-fetch"; | ||
const { client } = createGraphQLClient("MY_GRAPHQL_URL", { | ||
websocket: WebSocket, | ||
wsSubprotocol: "graphql-transport-ws", | ||
createHTTPLink: false | ||
}); | ||
### Node.js with WS link and [graphql-ws subprotocol](https://github.com/enisdenjo/graphql-ws/issues/154) (default) | ||
import { createGraphQLClient } from "graphql-ws"; | ||
import WebSocket from "ws"; | ||
import fetch from "node-fetch"; | ||
const { client } = createGraphQLClient("MY_GRAPHQL_URL", { | ||
websocket: WebSocket, | ||
wsSubprotocol: "graphql-ws", | ||
createHTTPLink: false | ||
}); | ||
### React with HTTP and WS links | ||
@@ -42,0 +80,0 @@ |
@@ -5,12 +5,15 @@ import { getMainDefinition } from "@apollo/client/utilities/utilities.cjs"; | ||
import { HttpLink } from "@apollo/client/link/http/http.cjs"; | ||
import { setContext } from '@apollo/client/link/context/context.cjs'; | ||
import { WebSocketLink } from "@apollo/client/link/ws/ws.cjs"; | ||
import { GraphQLWsLink } from "@apollo/client/link/subscriptions/subscriptions.cjs"; | ||
import { SubscriptionClient } from "subscriptions-transport-ws"; | ||
import { createClient } from "graphql-ws"; | ||
// TODO allow override of fetch options as in https://www.apollographql.com/docs/link/links/http/ | ||
export default (graphQLURL, passedOptions) => { | ||
const options = { | ||
export default (graphQLURL, options) => { | ||
const mergedOptions = { | ||
createHTTPLink: true, | ||
createWSLink: true, | ||
...passedOptions | ||
...options | ||
}; | ||
@@ -23,6 +26,11 @@ | ||
let httpLink = null; | ||
if(options.createHTTPLink) { | ||
if(mergedOptions.createHTTPLink) { | ||
const authLink = mergedOptions.httpLinkOptions?.setContext | ||
? setContext(mergedOptions.httpLinkOptions?.setContext) | ||
: undefined; | ||
const httpLinkOptions = { | ||
uri: graphQLURL, | ||
...options.httpLinkOptions | ||
...mergedOptions.httpLinkOptions | ||
}; | ||
@@ -33,3 +41,5 @@ | ||
httpLink = new HttpLink(httpLinkOptions); | ||
httpLink = authLink | ||
? authLink.concat(new HttpLink(httpLinkOptions)) // add auth link before http link | ||
: new HttpLink(httpLinkOptions); // no auth link | ||
} | ||
@@ -39,6 +49,35 @@ | ||
let subscriptionClient = null; | ||
if(options.createWSLink) { | ||
if(mergedOptions.createWSLink) { | ||
const wsSubprotocol = mergedOptions.wsSubprotocol | ||
? mergedOptions.wsSubprotocol.toLowerCase() | ||
: 'graphql-ws'; | ||
const wsLinkOptions = { | ||
reconnect: true, | ||
...options.wsLinkOptions | ||
...mergedOptions.wsLinkOptions, | ||
// override connectionParams to add subprotocol header | ||
connectionParams: async (...args) => { // add subprotocol | ||
console.log("CP"); | ||
const params = mergedOptions.wsLinkOptions?.connectionParams | ||
? await mergedOptions.wsLinkOptions?.connectionParams(...args) | ||
: {}; | ||
// merge in subprotocol headers | ||
console.log("SENDING", { | ||
...params, | ||
headers: { | ||
"sec-websocket-protocol": wsSubprotocol, | ||
...params.headers | ||
} | ||
}); | ||
return { | ||
...params, | ||
headers: { | ||
"sec-websocket-protocol": wsSubprotocol, | ||
...params.headers | ||
} | ||
} | ||
} | ||
}; | ||
@@ -49,7 +88,20 @@ | ||
const websocketImplementation = (typeof options.websocket === 'function') ? options.websocket : window.WebSocket; | ||
const websocketImplementation = typeof options.websocket === 'function' | ||
? options.websocket | ||
: window.WebSocket; | ||
subscriptionClient = new SubscriptionClient(httpURLToWS(graphQLURL), wsLinkOptions, websocketImplementation); | ||
wsLink = new WebSocketLink(subscriptionClient); | ||
if(wsSubprotocol === 'graphql-transport-ws') { // graphql-transport-ws, from graphql-ws | ||
subscriptionClient = createClient({ | ||
webSocketImpl: websocketImplementation, | ||
url: httpURLToWS(graphQLURL), | ||
...wsLinkOptions | ||
}); | ||
wsLink = new GraphQLWsLink(subscriptionClient); | ||
} | ||
else if(wsSubprotocol === 'graphql-ws') { // graphql-ws, from subscriptions-transport-ws | ||
subscriptionClient = new SubscriptionClient(httpURLToWS(graphQLURL), wsLinkOptions, websocketImplementation); | ||
wsLink = new WebSocketLink(subscriptionClient); | ||
} | ||
else | ||
throw new Error(`Unknown wsSubprotocol`); | ||
} | ||
@@ -64,3 +116,4 @@ | ||
wsLink, | ||
httpLink); | ||
httpLink | ||
); | ||
} | ||
@@ -81,3 +134,5 @@ else if(wsLink !== null) // only websocketLink exists | ||
return { | ||
link: options.hasOwnProperty('middleware') ? concat(options.middleware, link) : link, | ||
link: options.hasOwnProperty('middleware') | ||
? concat(options.middleware, link) | ||
: link, | ||
httpLink: httpLink, | ||
@@ -89,2 +144,2 @@ wsLink: wsLink, | ||
}; | ||
} | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
117555
3025
144
0
7