- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
460 lines
13 KiB
JavaScript
460 lines
13 KiB
JavaScript
import { __toESM, getErrorShape, require_objectSpread2 } from "./getErrorShape-vC8mUXJD.mjs";
|
|
import { isAsyncIterable, isObject, run } from "./codes-DagpWZLc.mjs";
|
|
import { TRPCError, callProcedure, getTRPCErrorFromUnknown, isTrackedEnvelope, transformTRPCResponse } from "./tracked-DiE3uR1B.mjs";
|
|
import { parseTRPCMessage } from "./parseTRPCMessage-CTow-umk.mjs";
|
|
import { Unpromise, iteratorResource, parseConnectionParamsFromUnknown, require_usingCtx } from "./resolveResponse-C5I6V_wc.mjs";
|
|
import { isObservable, observableToAsyncIterable } from "./observable-UMO3vUa_.mjs";
|
|
import { createURL } from "./node-http-CPiHo2kI.mjs";
|
|
|
|
//#region src/adapters/wsEncoder.ts
|
|
/**
|
|
* Default JSON encoder - used when no encoder is specified.
|
|
* This maintains backwards compatibility with existing behavior.
|
|
*/
|
|
const jsonEncoder = {
|
|
encode: (data) => JSON.stringify(data),
|
|
decode: (data) => {
|
|
if (typeof data !== "string") throw new Error("jsonEncoder received binary data. JSON uses text frames. Use a binary encoder for binary data.");
|
|
return JSON.parse(data);
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/adapters/ws.ts
|
|
var import_objectSpread2 = __toESM(require_objectSpread2(), 1);
|
|
var import_usingCtx = __toESM(require_usingCtx(), 1);
|
|
/**
|
|
* Importing ws causes a build error
|
|
* @see https://github.com/trpc/trpc/pull/5279
|
|
*/
|
|
const WEBSOCKET_OPEN = 1;
|
|
function getWSConnectionHandler(opts) {
|
|
var _opts$experimental_en;
|
|
const { createContext, router } = opts;
|
|
const { transformer } = router._def._config;
|
|
const encoder = (_opts$experimental_en = opts.experimental_encoder) !== null && _opts$experimental_en !== void 0 ? _opts$experimental_en : jsonEncoder;
|
|
return (client, req) => {
|
|
var _opts$keepAlive;
|
|
const clientSubscriptions = /* @__PURE__ */ new Map();
|
|
const abortController = new AbortController();
|
|
if ((_opts$keepAlive = opts.keepAlive) === null || _opts$keepAlive === void 0 ? void 0 : _opts$keepAlive.enabled) {
|
|
const { pingMs, pongWaitMs } = opts.keepAlive;
|
|
handleKeepAlive(client, pingMs, pongWaitMs);
|
|
}
|
|
function respond(untransformedJSON) {
|
|
client.send(encoder.encode(transformTRPCResponse(router._def._config, untransformedJSON)));
|
|
}
|
|
async function createCtxPromise(getConnectionParams) {
|
|
try {
|
|
return await run(async () => {
|
|
ctx = await (createContext === null || createContext === void 0 ? void 0 : createContext({
|
|
req,
|
|
res: client,
|
|
info: {
|
|
connectionParams: getConnectionParams(),
|
|
calls: [],
|
|
isBatchCall: false,
|
|
accept: null,
|
|
type: "unknown",
|
|
signal: abortController.signal,
|
|
url: null
|
|
}
|
|
}));
|
|
return {
|
|
ok: true,
|
|
value: ctx
|
|
};
|
|
});
|
|
} catch (cause) {
|
|
var _opts$onError, _globalThis$setImmedi;
|
|
const error = getTRPCErrorFromUnknown(cause);
|
|
(_opts$onError = opts.onError) === null || _opts$onError === void 0 || _opts$onError.call(opts, {
|
|
error,
|
|
path: void 0,
|
|
type: "unknown",
|
|
ctx,
|
|
req,
|
|
input: void 0
|
|
});
|
|
respond({
|
|
id: null,
|
|
error: getErrorShape({
|
|
config: router._def._config,
|
|
error,
|
|
type: "unknown",
|
|
path: void 0,
|
|
input: void 0,
|
|
ctx
|
|
})
|
|
});
|
|
((_globalThis$setImmedi = globalThis.setImmediate) !== null && _globalThis$setImmedi !== void 0 ? _globalThis$setImmedi : globalThis.setTimeout)(() => {
|
|
client.close();
|
|
});
|
|
return {
|
|
ok: false,
|
|
error
|
|
};
|
|
}
|
|
}
|
|
let ctx = void 0;
|
|
/**
|
|
* promise for initializing the context
|
|
*
|
|
* - the context promise will be created immediately on connection if no connectionParams are expected
|
|
* - if connection params are expected, they will be created once received
|
|
*/
|
|
let ctxPromise = createURL(req).searchParams.get("connectionParams") === "1" ? null : createCtxPromise(() => null);
|
|
function handleRequest(msg, batchIndex) {
|
|
const { id, jsonrpc } = msg;
|
|
if (id === null) {
|
|
var _opts$onError2;
|
|
const error = getTRPCErrorFromUnknown(new TRPCError({
|
|
code: "PARSE_ERROR",
|
|
message: "`id` is required"
|
|
}));
|
|
(_opts$onError2 = opts.onError) === null || _opts$onError2 === void 0 || _opts$onError2.call(opts, {
|
|
error,
|
|
path: void 0,
|
|
type: "unknown",
|
|
ctx,
|
|
req,
|
|
input: void 0
|
|
});
|
|
respond({
|
|
id,
|
|
jsonrpc,
|
|
error: getErrorShape({
|
|
config: router._def._config,
|
|
error,
|
|
type: "unknown",
|
|
path: void 0,
|
|
input: void 0,
|
|
ctx
|
|
})
|
|
});
|
|
return;
|
|
}
|
|
if (msg.method === "subscription.stop") {
|
|
var _clientSubscriptions$;
|
|
(_clientSubscriptions$ = clientSubscriptions.get(id)) === null || _clientSubscriptions$ === void 0 || _clientSubscriptions$.abort();
|
|
return;
|
|
}
|
|
const { path, lastEventId } = msg.params;
|
|
let { input } = msg.params;
|
|
const type = msg.method;
|
|
if (lastEventId !== void 0) if (isObject(input)) input = (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, input), {}, { lastEventId });
|
|
else {
|
|
var _input;
|
|
(_input = input) !== null && _input !== void 0 || (input = { lastEventId });
|
|
}
|
|
run(async () => {
|
|
const res = await ctxPromise;
|
|
if (!res.ok) throw res.error;
|
|
const abortController$1 = new AbortController();
|
|
const result = await callProcedure({
|
|
router,
|
|
path,
|
|
getRawInput: async () => input,
|
|
ctx,
|
|
type,
|
|
signal: abortController$1.signal,
|
|
batchIndex
|
|
});
|
|
const isIterableResult = isAsyncIterable(result) || isObservable(result);
|
|
if (type !== "subscription") {
|
|
if (isIterableResult) throw new TRPCError({
|
|
code: "UNSUPPORTED_MEDIA_TYPE",
|
|
message: `Cannot return an async iterable or observable from a ${type} procedure with WebSockets`
|
|
});
|
|
respond({
|
|
id,
|
|
jsonrpc,
|
|
result: {
|
|
type: "data",
|
|
data: result
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
if (!isIterableResult) throw new TRPCError({
|
|
message: `Subscription ${path} did not return an observable or a AsyncGenerator`,
|
|
code: "INTERNAL_SERVER_ERROR"
|
|
});
|
|
/* istanbul ignore next -- @preserve */
|
|
if (client.readyState !== WEBSOCKET_OPEN) return;
|
|
/* istanbul ignore next -- @preserve */
|
|
if (clientSubscriptions.has(id)) throw new TRPCError({
|
|
message: `Duplicate id ${id}`,
|
|
code: "BAD_REQUEST"
|
|
});
|
|
const iterable = isObservable(result) ? observableToAsyncIterable(result, abortController$1.signal) : result;
|
|
run(async () => {
|
|
try {
|
|
var _usingCtx = (0, import_usingCtx.default)();
|
|
const iterator = _usingCtx.a(iteratorResource(iterable));
|
|
const abortPromise = new Promise((resolve) => {
|
|
abortController$1.signal.onabort = () => resolve("abort");
|
|
});
|
|
let next;
|
|
let result$1;
|
|
while (true) {
|
|
next = await Unpromise.race([iterator.next().catch(getTRPCErrorFromUnknown), abortPromise]);
|
|
if (next === "abort") {
|
|
var _iterator$return;
|
|
await ((_iterator$return = iterator.return) === null || _iterator$return === void 0 ? void 0 : _iterator$return.call(iterator));
|
|
break;
|
|
}
|
|
if (next instanceof Error) {
|
|
var _opts$onError3;
|
|
const error = getTRPCErrorFromUnknown(next);
|
|
(_opts$onError3 = opts.onError) === null || _opts$onError3 === void 0 || _opts$onError3.call(opts, {
|
|
error,
|
|
path,
|
|
type,
|
|
ctx,
|
|
req,
|
|
input
|
|
});
|
|
respond({
|
|
id,
|
|
jsonrpc,
|
|
error: getErrorShape({
|
|
config: router._def._config,
|
|
error,
|
|
type,
|
|
path,
|
|
input,
|
|
ctx
|
|
})
|
|
});
|
|
break;
|
|
}
|
|
if (next.done) break;
|
|
result$1 = {
|
|
type: "data",
|
|
data: next.value
|
|
};
|
|
if (isTrackedEnvelope(next.value)) {
|
|
const [id$1, data] = next.value;
|
|
result$1.id = id$1;
|
|
result$1.data = {
|
|
id: id$1,
|
|
data
|
|
};
|
|
}
|
|
respond({
|
|
id,
|
|
jsonrpc,
|
|
result: result$1
|
|
});
|
|
next = null;
|
|
result$1 = null;
|
|
}
|
|
respond({
|
|
id,
|
|
jsonrpc,
|
|
result: { type: "stopped" }
|
|
});
|
|
clientSubscriptions.delete(id);
|
|
} catch (_) {
|
|
_usingCtx.e = _;
|
|
} finally {
|
|
await _usingCtx.d();
|
|
}
|
|
}).catch((cause) => {
|
|
var _opts$onError4;
|
|
const error = getTRPCErrorFromUnknown(cause);
|
|
(_opts$onError4 = opts.onError) === null || _opts$onError4 === void 0 || _opts$onError4.call(opts, {
|
|
error,
|
|
path,
|
|
type,
|
|
ctx,
|
|
req,
|
|
input
|
|
});
|
|
respond({
|
|
id,
|
|
jsonrpc,
|
|
error: getErrorShape({
|
|
config: router._def._config,
|
|
error,
|
|
type,
|
|
path,
|
|
input,
|
|
ctx
|
|
})
|
|
});
|
|
abortController$1.abort();
|
|
});
|
|
clientSubscriptions.set(id, abortController$1);
|
|
respond({
|
|
id,
|
|
jsonrpc,
|
|
result: { type: "started" }
|
|
});
|
|
}).catch((cause) => {
|
|
var _opts$onError5;
|
|
const error = getTRPCErrorFromUnknown(cause);
|
|
(_opts$onError5 = opts.onError) === null || _opts$onError5 === void 0 || _opts$onError5.call(opts, {
|
|
error,
|
|
path,
|
|
type,
|
|
ctx,
|
|
req,
|
|
input
|
|
});
|
|
respond({
|
|
id,
|
|
jsonrpc,
|
|
error: getErrorShape({
|
|
config: router._def._config,
|
|
error,
|
|
type,
|
|
path,
|
|
input,
|
|
ctx
|
|
})
|
|
});
|
|
});
|
|
}
|
|
client.on("message", (rawData, isBinary) => {
|
|
if (!isBinary) {
|
|
const msgStr = rawData.toString();
|
|
if (msgStr === "PONG") return;
|
|
if (msgStr === "PING") {
|
|
if (!opts.dangerouslyDisablePong) client.send("PONG");
|
|
return;
|
|
}
|
|
}
|
|
if (!Buffer.isBuffer(rawData)) {
|
|
const error = new TRPCError({
|
|
code: "UNPROCESSABLE_CONTENT",
|
|
message: "Unexpected WebSocket message format"
|
|
});
|
|
respond({
|
|
id: null,
|
|
error: getErrorShape({
|
|
config: router._def._config,
|
|
error,
|
|
type: "unknown",
|
|
path: void 0,
|
|
input: void 0,
|
|
ctx
|
|
})
|
|
});
|
|
return;
|
|
}
|
|
const data = isBinary ? rawData : rawData.toString("utf8");
|
|
if (!ctxPromise) {
|
|
ctxPromise = createCtxPromise(() => {
|
|
let msg;
|
|
try {
|
|
msg = encoder.decode(data);
|
|
if (!isObject(msg)) throw new Error("Message was not an object");
|
|
} catch (cause) {
|
|
throw new TRPCError({
|
|
code: "PARSE_ERROR",
|
|
message: `Malformed TRPCConnectionParamsMessage`,
|
|
cause
|
|
});
|
|
}
|
|
const connectionParams = parseConnectionParamsFromUnknown(msg.data);
|
|
return connectionParams;
|
|
});
|
|
return;
|
|
}
|
|
const parsedMsgs = run(() => {
|
|
try {
|
|
const msgJSON = encoder.decode(data);
|
|
const msgs = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
|
|
return msgs.map((raw) => parseTRPCMessage(raw, transformer));
|
|
} catch (cause) {
|
|
const error = new TRPCError({
|
|
code: "PARSE_ERROR",
|
|
cause
|
|
});
|
|
respond({
|
|
id: null,
|
|
error: getErrorShape({
|
|
config: router._def._config,
|
|
error,
|
|
type: "unknown",
|
|
path: void 0,
|
|
input: void 0,
|
|
ctx
|
|
})
|
|
});
|
|
return [];
|
|
}
|
|
});
|
|
parsedMsgs.map((msg, index) => handleRequest(msg, index));
|
|
});
|
|
client.on("error", (cause) => {
|
|
var _opts$onError6;
|
|
(_opts$onError6 = opts.onError) === null || _opts$onError6 === void 0 || _opts$onError6.call(opts, {
|
|
ctx,
|
|
error: getTRPCErrorFromUnknown(cause),
|
|
input: void 0,
|
|
path: void 0,
|
|
type: "unknown",
|
|
req
|
|
});
|
|
});
|
|
client.once("close", () => {
|
|
for (const sub of clientSubscriptions.values()) sub.abort();
|
|
clientSubscriptions.clear();
|
|
abortController.abort();
|
|
});
|
|
};
|
|
}
|
|
/**
|
|
* Handle WebSocket keep-alive messages
|
|
*/
|
|
function handleKeepAlive(client, pingMs = 3e4, pongWaitMs = 5e3) {
|
|
let timeout = void 0;
|
|
let ping = void 0;
|
|
const schedulePing = () => {
|
|
const scheduleTimeout = () => {
|
|
timeout = setTimeout(() => {
|
|
client.terminate();
|
|
}, pongWaitMs);
|
|
};
|
|
ping = setTimeout(() => {
|
|
client.send("PING");
|
|
scheduleTimeout();
|
|
}, pingMs);
|
|
};
|
|
const onMessage = () => {
|
|
clearTimeout(ping);
|
|
clearTimeout(timeout);
|
|
schedulePing();
|
|
};
|
|
client.on("message", onMessage);
|
|
client.on("close", () => {
|
|
clearTimeout(ping);
|
|
clearTimeout(timeout);
|
|
});
|
|
schedulePing();
|
|
}
|
|
function applyWSSHandler(opts) {
|
|
var _opts$experimental_en2;
|
|
const encoder = (_opts$experimental_en2 = opts.experimental_encoder) !== null && _opts$experimental_en2 !== void 0 ? _opts$experimental_en2 : jsonEncoder;
|
|
const onConnection = getWSConnectionHandler(opts);
|
|
opts.wss.on("connection", (client, req) => {
|
|
var _req$url;
|
|
if (opts.prefix && !((_req$url = req.url) === null || _req$url === void 0 ? void 0 : _req$url.startsWith(opts.prefix))) return;
|
|
onConnection(client, req);
|
|
});
|
|
return { broadcastReconnectNotification: () => {
|
|
const response = {
|
|
id: null,
|
|
method: "reconnect"
|
|
};
|
|
const data = encoder.encode(response);
|
|
for (const client of opts.wss.clients) if (client.readyState === WEBSOCKET_OPEN) client.send(data);
|
|
} };
|
|
}
|
|
|
|
//#endregion
|
|
export { applyWSSHandler, getWSConnectionHandler, handleKeepAlive, jsonEncoder };
|
|
//# sourceMappingURL=ws-CnhJbg02.mjs.map
|