UNPKG

convex

Version:

Client for the Convex Cloud

305 lines (304 loc) 10.3 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var networkTest_exports = {}; __export(networkTest_exports, { runNetworkTestOnUrl: () => runNetworkTestOnUrl, withTimeout: () => withTimeout }); module.exports = __toCommonJS(networkTest_exports); var import_context = require("../../bundler/context.js"); var import_chalk = __toESM(require("chalk"), 1); var net = __toESM(require("net"), 1); var dns = __toESM(require("dns"), 1); var crypto = __toESM(require("crypto"), 1); var import_utils = require("./utils/utils.js"); var import_ws = __toESM(require("ws"), 1); var import_browser = require("../../browser/index.js"); var import_logging = require("../../browser/logging.js"); const ipFamilyNumbers = { ipv4: 4, ipv6: 6, auto: 0 }; const ipFamilyNames = { 4: "ipv4", 6: "ipv6", 0: "auto" }; async function runNetworkTestOnUrl(ctx, { url, adminKey }, options) { await checkDns(ctx, url); await checkTcp(ctx, url, options.ipFamily ?? "auto"); await checkHttp(ctx, url); await checkWs(ctx, { url, adminKey }); await checkEcho(ctx, url, 128); await checkEcho(ctx, url, 4 * 1024 * 1024); if (options.speedTest) { await checkEcho(ctx, url, 64 * 1024 * 1024); } (0, import_context.logFinishedStep)(ctx, "Network test passed."); } async function checkDns(ctx, url) { try { const hostname = new URL("/", url).hostname; const start = performance.now(); const result = await new Promise((resolve, reject) => { dns.lookup(hostname, (err, address, family) => { if (err) { reject(err); } else { resolve({ duration: performance.now() - start, address, family }); } }); }); (0, import_context.logMessage)( ctx, `${import_chalk.default.green(`\u2714`)} OK: DNS lookup => ${result.address}:${ipFamilyNames[result.family]} (${(0, import_utils.formatDuration)(result.duration)})` ); } catch (e) { return ctx.crash({ exitCode: 1, errorType: "transient", printedMessage: `FAIL: DNS lookup (${e})` }); } } async function checkTcp(ctx, urlString, ipFamilyOpt) { const url = new URL(urlString); if (url.protocol === "http:") { const port = Number.parseInt(url.port || "80"); await checkTcpHostPort(ctx, url.hostname, port, ipFamilyOpt); } else if (url.protocol === "https:") { const port = Number.parseInt(url.port || "443"); await checkTcpHostPort(ctx, url.hostname, port, ipFamilyOpt); if (!url.port) { await checkTcpHostPort(ctx, url.hostname, 80, ipFamilyOpt); } } else { throw new Error(`Unknown protocol: ${url.protocol}`); } } async function checkTcpHostPort(ctx, host, port, ipFamilyOpt) { const ipFamily = ipFamilyNumbers[ipFamilyOpt]; const tcpString = `TCP` + (ipFamilyOpt === "auto" ? "" : `/${ipFamilyOpt} ${host}:${port}`); try { const start = performance.now(); const duration = await new Promise((resolve, reject) => { const socket = net.connect( { host, port, noDelay: true, family: ipFamily }, () => resolve(performance.now() - start) ); socket.on("error", (e) => reject(e)); }); (0, import_context.logMessage)( ctx, `${import_chalk.default.green(`\u2714`)} OK: ${tcpString} connect (${(0, import_utils.formatDuration)( duration )})` ); } catch (e) { return ctx.crash({ exitCode: 1, errorType: "transient", printedMessage: `FAIL: ${tcpString} connect (${e})` }); } } async function checkHttp(ctx, urlString) { const url = new URL(urlString); const isHttps = url.protocol === "https:"; if (isHttps) { url.protocol = "http:"; url.port = "80"; await checkHttpOnce(ctx, "HTTP", url.toString(), false); } await checkHttpOnce(ctx, isHttps ? "HTTPS" : "HTTP", urlString, true); } async function checkHttpOnce(ctx, name, url, allowRedirects) { const start = performance.now(); try { const fetch = (0, import_utils.bareDeploymentFetch)(ctx, { deploymentUrl: url }); const instanceNameUrl = new URL("/instance_name", url); const resp = await fetch(instanceNameUrl.toString(), { redirect: allowRedirects ? "follow" : "manual" }); if (resp.status !== 200) { throw new Error(`Unexpected status code: ${resp.status}`); } } catch (e) { const isOkayRedirect = !allowRedirects && e instanceof import_utils.ThrowingFetchError && e.response.status === 301; if (!isOkayRedirect) { return ctx.crash({ exitCode: 1, errorType: "transient", printedMessage: `FAIL: ${name} check (${e})` }); } } const duration = performance.now() - start; (0, import_context.logMessage)( ctx, `${import_chalk.default.green(`\u2714`)} OK: ${name} check (${(0, import_utils.formatDuration)(duration)})` ); } async function checkWs(ctx, { url, adminKey }) { if (adminKey === null) { (0, import_context.logWarning)( ctx, "Skipping WebSocket check because no admin key was provided." ); return; } let queryPromiseResolver = null; const queryPromise = new Promise((resolve) => { queryPromiseResolver = resolve; }); const logger = new import_logging.Logger({ verbose: process.env.CONVEX_VERBOSE !== void 0 }); logger.addLogLineListener((level, ...args) => { switch (level) { case "debug": (0, import_context.logVerbose)(ctx, ...args); break; case "info": (0, import_context.logVerbose)(ctx, ...args); break; case "warn": (0, import_context.logWarning)(ctx, ...args); break; case "error": (0, import_context.logWarning)(ctx, ...args); break; } }); const convexClient = new import_browser.BaseConvexClient( url, (updatedQueries) => { for (const queryToken of updatedQueries) { const result = convexClient.localQueryResultByToken(queryToken); if (typeof result === "string" && queryPromiseResolver !== null) { queryPromiseResolver(result); queryPromiseResolver = null; } } }, { webSocketConstructor: import_ws.default, unsavedChangesWarning: false, logger } ); convexClient.setAdminAuth(adminKey); convexClient.subscribe("_system/cli/convexUrl:cloudUrl", {}); const racePromise = Promise.race([ queryPromise, new Promise((resolve) => setTimeout(() => resolve(null), 1e4)) ]); const cloudUrl = await racePromise; if (cloudUrl === null) { return ctx.crash({ exitCode: 1, errorType: "transient", printedMessage: "FAIL: Failed to connect to deployment over WebSocket." }); } else { (0, import_context.logMessage)( ctx, `${import_chalk.default.green(`\u2714`)} OK: WebSocket connection established.` ); } } async function checkEcho(ctx, url, size) { try { const start = performance.now(); const fetch = (0, import_utils.bareDeploymentFetch)(ctx, { deploymentUrl: url, onError: (err) => { (0, import_context.logFailure)( ctx, import_chalk.default.red(`FAIL: echo ${(0, import_utils.formatSize)(size)} (${err}), retrying...`) ); } }); const echoUrl = new URL(`/echo`, url); const data = crypto.randomBytes(size); const resp = await fetch(echoUrl.toString(), { body: data, method: "POST" }); if (resp.status !== 200) { throw new Error(`Unexpected status code: ${resp.status}`); } const respData = await resp.arrayBuffer(); if (!data.equals(Buffer.from(respData))) { throw new Error(`Response data mismatch`); } const duration = performance.now() - start; const bytesPerSecond = size / (duration / 1e3); (0, import_context.logMessage)( ctx, `${import_chalk.default.green(`\u2714`)} OK: echo ${(0, import_utils.formatSize)(size)} (${(0, import_utils.formatDuration)( duration )}, ${(0, import_utils.formatSize)(bytesPerSecond)}/s)` ); } catch (e) { return ctx.crash({ exitCode: 1, errorType: "transient", printedMessage: `FAIL: echo ${(0, import_utils.formatSize)(size)} (${e})` }); } } async function withTimeout(ctx, name, timeoutMs, f) { let timer = null; try { const result = await Promise.race([ f.then((r) => { return { kind: "ok", result: r }; }), new Promise((resolve) => { timer = setTimeout(() => { resolve({ kind: "timeout" }); timer = null; }, timeoutMs); }) ]); if (result.kind === "ok") { return result.result; } else { return await ctx.crash({ exitCode: 1, errorType: "transient", printedMessage: `FAIL: ${name} timed out after ${(0, import_utils.formatDuration)(timeoutMs)}.` }); } } finally { if (timer !== null) { clearTimeout(timer); } } } //# sourceMappingURL=networkTest.js.map