@web/dev-server-core
Version:
265 lines (246 loc) • 8.81 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.webSocketsPlugin = exports.webSocketScript = void 0;
const WebSocketsManager_js_1 = require("./WebSocketsManager.js");
const parse5_utils_1 = require("@web/parse5-utils");
exports.webSocketScript = `<!-- injected by web-dev-server -->
<script type="module" src="${WebSocketsManager_js_1.NAME_WEB_SOCKET_IMPORT}"></script>`;
function webSocketsPlugin() {
return {
name: 'web-sockets',
resolveImport({ source }) {
if (source === WebSocketsManager_js_1.NAME_WEB_SOCKET_IMPORT) {
return WebSocketsManager_js_1.NAME_WEB_SOCKET_IMPORT;
}
},
serve(context) {
if (context.path === WebSocketsManager_js_1.NAME_WEB_SOCKET_IMPORT) {
// this code is inlined because TS compiles to CJS but we need this to be ESM
return `
/**
* Code at this indent adapted from fast-safe-stringify by David Mark Clements
* @license MIT
* @see https://github.com/davidmarkclements/fast-safe-stringify
*/
var arr = []
var replacerStack = []
// Stable-stringify
function compareFunction (a, b) {
if (a < b) {
return -1
}
if (a > b) {
return 1
}
return 0
}
export function stable (obj, replacer, spacer) {
var target = structuredClone(obj)
var tmp = deterministicDecirc(target, '', [], undefined) || target
var res
if (replacerStack.length === 0) {
res = JSON.stringify(tmp, replacer, spacer)
} else {
res = JSON.stringify(tmp, replaceGetterValues(replacer), spacer)
}
while (arr.length !== 0) {
var part = arr.pop()
if (part.length === 4) {
Object.defineProperty(part[0], part[1], part[3])
} else {
part[0][part[1]] = part[2]
}
}
return res
}
function deterministicDecirc (val, k, stack, parent) {
var i
if (typeof val === 'object' && val !== null) {
for (i = 0; i < stack.length; i++) {
if (stack[i] === val) {
var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k)
if (propertyDescriptor.get !== undefined) {
if (propertyDescriptor.configurable) {
Object.defineProperty(parent, k, { value: '[Circular]' })
arr.push([parent, k, val, propertyDescriptor])
} else {
replacerStack.push([val, k])
}
} else {
parent[k] = '[Circular]'
arr.push([parent, k, val])
}
return
}
}
if (typeof val.toJSON === 'function') {
return
}
stack.push(val)
// Optimize for Arrays. Big arrays could kill the performance otherwise!
if (Array.isArray(val)) {
for (i = 0; i < val.length; i++) {
deterministicDecirc(val[i], i, stack, val)
}
} else {
// Create a temporary object in the required way
var tmp = {}
var keys = Object.keys(val).sort(compareFunction)
for (i = 0; i < keys.length; i++) {
var key = keys[i]
deterministicDecirc(val[key], key, stack, val)
tmp[key] = val[key]
}
if (parent !== undefined) {
arr.push([parent, k, val])
parent[k] = tmp
} else {
return tmp
}
}
stack.pop()
}
}
// wraps replacer function to handle values we couldn't replace
// and mark them as [Circular]
function replaceGetterValues (replacer) {
replacer = replacer !== undefined ? replacer : function (k, v) { return v }
return function (key, val) {
if (replacerStack.length > 0) {
for (var i = 0; i < replacerStack.length; i++) {
var part = replacerStack[i]
if (part[1] === key && part[0] === val) {
val = '[Circular]'
replacerStack.splice(i, 1)
break
}
}
}
return replacer.call(this, key, val)
}
}
const { protocol, host } = new URL(import.meta.url);
const webSocketUrl = \`ws\${protocol === 'https:' ? 's' : ''}://\${host}/${WebSocketsManager_js_1.NAME_WEB_SOCKET_API}\`;
export let webSocket;
export let webSocketOpened;
export let sendMessage;
export let sendMessageWaitForResponse;
let getNextMessageId;
function setupFetch() {
sendMessage = (message) =>fetch('/__web-test-runner__/wtr-legacy-browser-api', { method: 'POST', body: stable(message) });
sendMessageWaitForResponse = (message) => fetch('/__web-test-runner__/wtr-legacy-browser-api', { method: 'POST', body: stable(message) });
}
function setupWebSocket() {
let useParent = false;
try {
// if window is an iframe and accessing a cross origin frame is not allowed this will throw
// therefore we try/catch it here so it does not disable all web sockets
if (window.parent !== window && window.parent.__WDS_WEB_SOCKET__ !== undefined) {
useParent = true;
}
} catch(e) {}
if (useParent) {
// get the websocket instance from the parent element if present
const info = window.parent.__WDS_WEB_SOCKET__;
webSocket = info.webSocket;
webSocketOpened = info.webSocketOpened;
getNextMessageId = info.getNextMessageId;
} else {
webSocket =
'WebSocket' in window
? new WebSocket(webSocketUrl)
: null;
webSocketOpened = new Promise(resolve => {
if (!webSocket) {
resolve();
} else {
webSocket.addEventListener('open', () => {
resolve();
});
}
});
let messageId = 0;
getNextMessageId = function () {
if (messageId >= Number.MAX_SAFE_INTEGER) {
messageId = 0;
}
messageId += 1;
return messageId;
};
window.__WDS_WEB_SOCKET__ = { webSocket, webSocketOpened, getNextMessageId };
}
sendMessage = async (message) => {
if (!message.type) {
throw new Error('Missing message type');
}
await webSocketOpened;
webSocket.send(stable(message));
}
// sends a websocket message and expects a response from the server
sendMessageWaitForResponse = async (message) => {
return new Promise(async (resolve, reject) => {
const id = getNextMessageId();
function onResponse(e) {
const message = JSON.parse(e.data);
if (message.type === 'message-response' && message.id === id) {
webSocket.removeEventListener('message', onResponse);
if (message.error) {
reject(new Error(message.error));
} else {
resolve(message.response);
}
}
}
webSocket.addEventListener('message', onResponse);
setTimeout(() => {
webSocket.removeEventListener('message', onResponse);
reject(
new Error(
\`Did not receive a server response for message with type \${message.type} within 20000ms\`,
),
);
}, 20000);
sendMessage({ ...message, id });
});
}
if (webSocket) {
webSocket.addEventListener('message', async e => {
try {
const message = JSON.parse(e.data);
if (message.type === 'import') {
const module = await import(message.data.importPath);
if (typeof module.default === 'function') {
module.default(...(message.data.args || []));
}
return;
}
} catch (error) {
console.error('[Web Dev Server] Error while handling websocket message.');
console.error(error);
}
});
}
}
if (!!navigator.userAgent.match(/Trident/)) {
setupFetch();
} else {
setupWebSocket();
}
`;
}
},
async transform(context) {
if (context.response.is('html')) {
if (typeof context.body !== 'string') {
return;
}
if ((0, parse5_utils_1.isHtmlFragment)(context.body)) {
return;
}
return (0, parse5_utils_1.appendToDocument)(context.body, exports.webSocketScript);
}
},
};
}
exports.webSocketsPlugin = webSocketsPlugin;
//# sourceMappingURL=webSocketsPlugin.js.map