Skip to content

Instantly share code, notes, and snippets.

@flaviut
Last active September 27, 2020 00:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flaviut/fd89b3026826c18623a09cacd68d05b2 to your computer and use it in GitHub Desktop.
Save flaviut/fd89b3026826c18623a09cacd68d05b2 to your computer and use it in GitHub Desktop.
// generates a websocket for a sockjs endpoint, as defined in
// https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html
//
// This websocket works just like a regular websocket, and is compatible with
// stompjs.
//
// `onmessage` and `send()` are modified to transparently handle converting to and
// from sockjs messages
//
// (c) 2020, Flaviu Tamas
// https://gist.github.com/flaviut/fd89b3026826c18623a09cacd68d05b2
// SPDX-License-Identifier: MIT
export function sockJsFactory(url: string): WebSocket {
// technically we're supposed to fetch '${url}/info' to figure out if the
// server supports websockets. But we're just going to go ahead and assume
// that's the case, since we don't have a good way of handling that error.
const serverId = String(randomInteger(0, 1000)).padStart(3, '0');
const sessionId = randomStr(12);
const socketUrl = new URL(url)
socketUrl.pathname = `${socketUrl.pathname}/${serverId}/${sessionId}/websocket`
if (socketUrl.protocol.startsWith('http')) {
socketUrl.protocol = socketUrl.protocol === 'http:' ? 'ws:' : 'wss:';
}
const socket = new WebSocket(socketUrl.toString())
const handler: ProxyHandler<WebSocket> = {
set: (obj, prop, value) => {
if (prop === 'onmessage' && value != null) {
obj[prop] = (ev: MessageEvent) => {
const data: string = ev.data;
// message frames start with "a", see
// https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-42
if (data.startsWith('a')) {
JSON.parse(data.substring(1))
.forEach((submsg: string) => {
(value as ((ev: MessageEvent) => void)).call(
obj as any,
new MessageEvent(ev.type, {
data: submsg,
lastEventId: ev.lastEventId,
origin: ev.origin,
ports: ev.ports,
source: ev.source,
bubbles: ev.bubbles,
cancelable: ev.cancelable,
composed: ev.composed,
} as MessageEventInit)
)
});
}
};
return true;
}
(obj as any)[prop] = value;
return true
},
get: (target, key) => {
if (key === "send") {
return (data: any) => target.send(JSON.stringify([data]));
}
const result = key in target ? (target as any)[key] : undefined;
if (typeof result === 'function') {
return (result as () => void).bind(target);
}
return result;
}
}
return new Proxy(socket, handler);
}
// from https://stackoverflow.com/a/55544949/2299084
function randomInteger(min: number, max: number): number {
const range = max - min;
const maxGeneratedValue = 0xFFFFFFFF;
const possibleResultValues = range + 1;
const possibleGeneratedValues = maxGeneratedValue + 1;
const remainder = possibleGeneratedValues % possibleResultValues;
const maxUnbiased = maxGeneratedValue - remainder;
if (!Number.isInteger(min) || !Number.isInteger(max) ||
max > Number.MAX_SAFE_INTEGER || min < Number.MIN_SAFE_INTEGER) {
throw new Error('Arguments must be safe integers.');
} else if (range > maxGeneratedValue) {
throw new Error(`Range of ${range} (from ${min} to ${max}) > ${maxGeneratedValue}.`);
} else if (max < min) {
throw new Error(`max (${max}) must be >= min (${min}).`);
} else if (min === max) {
return min;
}
let generated;
do {
generated = crypto.getRandomValues(new Uint32Array(1))[0];
} while (generated > maxUnbiased);
return min + (generated % possibleResultValues);
};
const dictionary = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
function randomStr(length: number = 12): string {
let result = "";
for (let i = 0; i < length; i++) {
result += dictionary[randomInteger(0, dictionary.length)];
}
return result
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment