@eulerstream/euler-websocket-sdk
Version:
Browser & Node.JS cross-compatible module for the Euler Stream WebSocket service.
210 lines (209 loc) • 7.88 kB
JavaScript
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 };