UNPKG

@eulerstream/euler-websocket-sdk

Version:

Browser & Node.JS cross-compatible module for the Euler Stream WebSocket service.

210 lines (209 loc) 7.88 kB
import "./chunk-ZRW5x4aP.js"; import * as tikTokSchemaV1 from "tiktok-live-proto/v1"; import * as tikTokSchemaV2 from "tiktok-live-proto/v2"; import { z } from "zod"; import EventEmitter from "eventemitter3"; export * from "tiktok-live-proto/v2"; //#region src/webcast/schemas.ts let SchemaVersion = /* @__PURE__ */ function(SchemaVersion) { SchemaVersion["v1"] = "v1"; SchemaVersion["v2"] = "v2"; return SchemaVersion; }({}); const WebcastSchemas = { ["v1"]: tikTokSchemaV1, ["v2"]: tikTokSchemaV2 }; const BASE_URL = typeof process !== "undefined" && process?.env != null ? process.env.EULER_WS_URL || "wss://ws.eulerstream.com" : "wss://ws.eulerstream.com"; /** * Given an input object, this function flattens it into a single-level object with dot-separated keys. * * @param obj The input object to flatten * @param prefix The prefix to prepend to each key (used for recursion); i.e. the current key path * @param result The result object that accumulates the flattened key-value pairs */ function flattenObject(obj, prefix = "", result = {}) { for (const key of Object.keys(obj)) { const value = obj[key]; const fullKey = prefix ? `${prefix}.${key}` : key; if (value !== null && typeof value === "object" && !Array.isArray(value)) flattenObject(value, fullKey, result); else if (value !== void 0) result[fullKey] = String(value); } return result; } /** * Creates a WebSocket URL with query parameters based on the provided context. * * @param options {WebSocketOptions} The context to include in the WebSocket URL */ function createWebSocketUrl(options) { const flatParams = flattenObject(options); return `${BASE_URL}?${new URLSearchParams(flatParams).toString()}`; } function normalizeUniqueId(uniqueId) { uniqueId = uniqueId.replace("https://www.tiktok.com/", ""); uniqueId = uniqueId.replace("/live", ""); uniqueId = uniqueId.replace("@", ""); uniqueId = uniqueId.trim(); return uniqueId; } //#endregion //#region src/extras/zod-extra.ts /** * Creates a Zod schema that coerces string values to boolean. * @param options The context for the coercion, including a default value. */ const coerceBoolean = (options) => { return z.enum([ "true", "false", "1", "0" ]).transform((val) => val === "true" || val === "1").default(String(options.default)); }; /** * Creates a Zod schema that coerces string values to number. * @param options The context for the coercion, including min, max, and default values. */ const coerceNumber = (options) => { return z.string().default(String(options.default)).refine((val) => !isNaN(Number(val)), { message: "Invalid number" }).transform((val) => Number(val)).refine((num) => options.min === void 0 || num >= options.min, { message: `Must be >= ${options.min}` }).refine((num) => options.max === void 0 || num <= options.max, { message: `Must be <= ${options.max}` }); }; //#endregion //#region src/client/types.ts let ClientCloseCode = /* @__PURE__ */ function(ClientCloseCode) { /** * Responding to a client's close request normally */ ClientCloseCode[ClientCloseCode["NORMAL"] = 1e3] = "NORMAL"; /** * Error updating presence on connect, or upstream error on connect in the proxy. */ ClientCloseCode[ClientCloseCode["INTERNAL_SERVER_ERROR"] = 1011] = "INTERNAL_SERVER_ERROR"; /** * Error fetching the /webcast/fetch endpoint for the socket */ ClientCloseCode[ClientCloseCode["WEBCAST_FETCH_ERROR"] = 4556] = "WEBCAST_FETCH_ERROR"; /** * Error fetching the /webcast/room_info endpoint for the socket */ ClientCloseCode[ClientCloseCode["ROOM_INFO_FETCH_ERROR"] = 4557] = "ROOM_INFO_FETCH_ERROR"; /** * TikTok closed the connected unexpectedly. */ ClientCloseCode[ClientCloseCode["TIKTOK_CLOSED_CONNECTION"] = 4500] = "TIKTOK_CLOSED_CONNECTION"; /** * The account has too many connections OR is connecting too quickly. */ ClientCloseCode[ClientCloseCode["TOO_MANY_CONNECTIONS"] = 4429] = "TOO_MANY_CONNECTIONS"; /** * The client provided invalid context, such as an invalid uniqueId or JWT key. */ ClientCloseCode[ClientCloseCode["INVALID_OPTIONS"] = 4400] = "INVALID_OPTIONS"; /** * The requested streamer is not live. */ ClientCloseCode[ClientCloseCode["NOT_LIVE"] = 4404] = "NOT_LIVE"; /** * The TikTok stream ended. */ ClientCloseCode[ClientCloseCode["STREAM_END"] = 4005] = "STREAM_END"; /** * There were no messages in the timeout period, the WebSocket was assumed dead and closed. */ ClientCloseCode[ClientCloseCode["NO_MESSAGES_TIMEOUT"] = 4006] = "NO_MESSAGES_TIMEOUT"; /** * Invalid Auth */ ClientCloseCode[ClientCloseCode["INVALID_AUTH"] = 4401] = "INVALID_AUTH"; /** * Accessing a creator the JWT has no access to */ ClientCloseCode[ClientCloseCode["NO_PERMISSION"] = 4403] = "NO_PERMISSION"; /** * WebSocket exceeded 8 hour lifetime */ ClientCloseCode[ClientCloseCode["MAX_LIFETIME_EXCEEDED"] = 4555] = "MAX_LIFETIME_EXCEEDED"; return ClientCloseCode; }({}); const CloseMessageMap = { [4557]: "Error fetching /webcast/room_info", [4556]: "Error fetching /webcast/fetch", [4401]: "Invalid auth", [4555]: "Max lifetime exceeded", [4403]: "No permission", [1011]: "Internal server error", [4500]: "TikTok closed the connection unexpectedly", [4429]: "Too many concurrent connections", [4400]: "Invalid context provided", [4404]: "Streamer is not live", [4005]: "TikTok stream ended", [4006]: "No messages received in timeout period, closing WebSocket", [1e3]: "Normal closure" }; const WebSocketFeatureFlags = z.object({ /** * When enabled, the client will bundle multiple messages into a single event. This is more efficient * than sending messages individually. */ bundleEvents: coerceBoolean({ default: true }), /** * When enabled, the client will act as a pass-through proxy for raw messages. You will lose out on * additional features like presence messages, but this will fit nicely into existing libraries. */ rawMessages: coerceBoolean({ default: false }), /** * Whether to normalize uniqueIds in URL format, @uniqueId format, etc., or treat them as-is. */ normalizeUniqueId: coerceBoolean({ default: true }), /** * When enabled, the client will calculate presence information for users in the room. * This enables us to give custom SyntheticJoinMessage and SyntheticLeaveMessage, a full presence system. */ syntheticPresence: coerceBoolean({ default: false }), /** * Configures how long a user must be inactive before we send a SyntheticLeaveMessage. */ syntheticPresenceLeaveAfter: coerceNumber({ default: 300, min: 60, max: 3600 }), /** * Configures how long we can wait with NO messages coming from TikTok before we assume the WebSocket * is dead and close it. */ closeInactiveWebSocketAfter: coerceNumber({ default: 60, min: 30, max: 3600 }), /** * The TikTok protobuf schema version to use for decoding messages. */ schemaVersion: z.nativeEnum(SchemaVersion).default("v2"), /** * Whether to add a "raw" entry including base64 encoded Protobuf with the JSON */ includeRawBytes: coerceBoolean({ default: false }), /** * Whether to use the Enterprise Sign API infrastructure (recommended) */ useEnterpriseApi: coerceBoolean({ default: false }), /** * Select the platform to connect with */ webcastPlatform: z.enum(["mobile", "web"]).default("web") }); const WebSocketOptionsSchema = z.object({ uniqueId: z.string(), jwtKey: z.string().optional().nullable(), apiKey: z.string().optional().nullable(), features: WebSocketFeatureFlags.default({}), sessionId: z.string().optional().nullable(), ttTargetIdc: z.string().optional().nullable() }); //#endregion //#region src/extras/typed-emitter.ts var WebcastEventEmitter = class extends EventEmitter {}; //#endregion export { BASE_URL, ClientCloseCode, CloseMessageMap, SchemaVersion, WebSocketFeatureFlags, WebSocketOptionsSchema, WebcastEventEmitter, WebcastSchemas, createWebSocketUrl, normalizeUniqueId };