UNPKG

@eulerstream/euler-websocket-sdk

Version:

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

378 lines (377 loc) 16.8 kB
import * as tikTokSchemaV1 from "tiktok-live-proto/v1"; import * as tiktokSchema from "tiktok-live-proto/v2"; import { MessageFns, ProtoMessageFetchResult, User, WebcastBarrageMessage, WebcastPushFrame } from "tiktok-live-proto/v2"; import { z } from "zod"; import EventEmitter from "eventemitter3"; export * from "tiktok-live-proto/v2"; //#region src/client/types.d.ts declare enum ClientCloseCode { /** * Responding to a client's close request normally */ NORMAL = 1000, /** * Error updating presence on connect, or upstream error on connect in the proxy. */ INTERNAL_SERVER_ERROR = 1011, /** * Error fetching the /webcast/fetch endpoint for the socket */ WEBCAST_FETCH_ERROR = 4556, /** * Error fetching the /webcast/room_info endpoint for the socket */ ROOM_INFO_FETCH_ERROR = 4557, /** * TikTok closed the connected unexpectedly. */ TIKTOK_CLOSED_CONNECTION = 4500, /** * The account has too many connections OR is connecting too quickly. */ TOO_MANY_CONNECTIONS = 4429, /** * The client provided invalid context, such as an invalid uniqueId or JWT key. */ INVALID_OPTIONS = 4400, /** * The requested streamer is not live. */ NOT_LIVE = 4404, /** * The TikTok stream ended. */ STREAM_END = 4005, /** * There were no messages in the timeout period, the WebSocket was assumed dead and closed. */ NO_MESSAGES_TIMEOUT = 4006, /** * Invalid Auth */ INVALID_AUTH = 4401, /** * Accessing a creator the JWT has no access to */ NO_PERMISSION = 4403, /** * WebSocket exceeded 8 hour lifetime */ MAX_LIFETIME_EXCEEDED = 4555 } declare const CloseMessageMap: Record<ClientCloseCode, string>; declare const WebSocketFeatureFlags: z.ZodObject<{ /** * When enabled, the client will bundle multiple messages into a single event. This is more efficient * than sending messages individually. */ bundleEvents: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * 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: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * Whether to normalize uniqueIds in URL format, @uniqueId format, etc., or treat them as-is. */ normalizeUniqueId: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * 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: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * Configures how long a user must be inactive before we send a SyntheticLeaveMessage. */ syntheticPresenceLeaveAfter: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>, number, string | undefined>, number, string | undefined>, number, string | undefined>; /** * Configures how long we can wait with NO messages coming from TikTok before we assume the WebSocket * is dead and close it. */ closeInactiveWebSocketAfter: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>, number, string | undefined>, number, string | undefined>, number, string | undefined>; /** * The TikTok protobuf schema version to use for decoding messages. */ schemaVersion: z.ZodDefault<z.ZodNativeEnum<typeof SchemaVersion>>; /** * Whether to add a "raw" entry including base64 encoded Protobuf with the JSON */ includeRawBytes: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * Whether to use the Enterprise Sign API infrastructure (recommended) */ useEnterpriseApi: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * Select the platform to connect with */ webcastPlatform: z.ZodDefault<z.ZodEnum<["mobile", "web"]>>; }, "strip", z.ZodTypeAny, { bundleEvents: boolean; rawMessages: boolean; normalizeUniqueId: boolean; syntheticPresence: boolean; syntheticPresenceLeaveAfter: number; closeInactiveWebSocketAfter: number; schemaVersion: SchemaVersion; includeRawBytes: boolean; useEnterpriseApi: boolean; webcastPlatform: "mobile" | "web"; }, { bundleEvents?: "0" | "true" | "false" | "1" | undefined; rawMessages?: "0" | "true" | "false" | "1" | undefined; normalizeUniqueId?: "0" | "true" | "false" | "1" | undefined; syntheticPresence?: "0" | "true" | "false" | "1" | undefined; syntheticPresenceLeaveAfter?: string | undefined; closeInactiveWebSocketAfter?: string | undefined; schemaVersion?: SchemaVersion | undefined; includeRawBytes?: "0" | "true" | "false" | "1" | undefined; useEnterpriseApi?: "0" | "true" | "false" | "1" | undefined; webcastPlatform?: "mobile" | "web" | undefined; }>; declare const WebSocketOptionsSchema: z.ZodObject<{ uniqueId: z.ZodString; jwtKey: z.ZodNullable<z.ZodOptional<z.ZodString>>; apiKey: z.ZodNullable<z.ZodOptional<z.ZodString>>; features: z.ZodDefault<z.ZodObject<{ /** * When enabled, the client will bundle multiple messages into a single event. This is more efficient * than sending messages individually. */ bundleEvents: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * 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: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * Whether to normalize uniqueIds in URL format, @uniqueId format, etc., or treat them as-is. */ normalizeUniqueId: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * 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: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * Configures how long a user must be inactive before we send a SyntheticLeaveMessage. */ syntheticPresenceLeaveAfter: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>, number, string | undefined>, number, string | undefined>, number, string | undefined>; /** * Configures how long we can wait with NO messages coming from TikTok before we assume the WebSocket * is dead and close it. */ closeInactiveWebSocketAfter: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>, number, string | undefined>, number, string | undefined>, number, string | undefined>; /** * The TikTok protobuf schema version to use for decoding messages. */ schemaVersion: z.ZodDefault<z.ZodNativeEnum<typeof SchemaVersion>>; /** * Whether to add a "raw" entry including base64 encoded Protobuf with the JSON */ includeRawBytes: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * Whether to use the Enterprise Sign API infrastructure (recommended) */ useEnterpriseApi: z.ZodDefault<z.ZodEffects<z.ZodEnum<["true", "false", "1", "0"]>, boolean, "0" | "true" | "false" | "1">>; /** * Select the platform to connect with */ webcastPlatform: z.ZodDefault<z.ZodEnum<["mobile", "web"]>>; }, "strip", z.ZodTypeAny, { bundleEvents: boolean; rawMessages: boolean; normalizeUniqueId: boolean; syntheticPresence: boolean; syntheticPresenceLeaveAfter: number; closeInactiveWebSocketAfter: number; schemaVersion: SchemaVersion; includeRawBytes: boolean; useEnterpriseApi: boolean; webcastPlatform: "mobile" | "web"; }, { bundleEvents?: "0" | "true" | "false" | "1" | undefined; rawMessages?: "0" | "true" | "false" | "1" | undefined; normalizeUniqueId?: "0" | "true" | "false" | "1" | undefined; syntheticPresence?: "0" | "true" | "false" | "1" | undefined; syntheticPresenceLeaveAfter?: string | undefined; closeInactiveWebSocketAfter?: string | undefined; schemaVersion?: SchemaVersion | undefined; includeRawBytes?: "0" | "true" | "false" | "1" | undefined; useEnterpriseApi?: "0" | "true" | "false" | "1" | undefined; webcastPlatform?: "mobile" | "web" | undefined; }>>; sessionId: z.ZodNullable<z.ZodOptional<z.ZodString>>; ttTargetIdc: z.ZodNullable<z.ZodOptional<z.ZodString>>; }, "strip", z.ZodTypeAny, { uniqueId: string; features: { bundleEvents: boolean; rawMessages: boolean; normalizeUniqueId: boolean; syntheticPresence: boolean; syntheticPresenceLeaveAfter: number; closeInactiveWebSocketAfter: number; schemaVersion: SchemaVersion; includeRawBytes: boolean; useEnterpriseApi: boolean; webcastPlatform: "mobile" | "web"; }; jwtKey?: string | null | undefined; apiKey?: string | null | undefined; sessionId?: string | null | undefined; ttTargetIdc?: string | null | undefined; }, { uniqueId: string; jwtKey?: string | null | undefined; apiKey?: string | null | undefined; features?: { bundleEvents?: "0" | "true" | "false" | "1" | undefined; rawMessages?: "0" | "true" | "false" | "1" | undefined; normalizeUniqueId?: "0" | "true" | "false" | "1" | undefined; syntheticPresence?: "0" | "true" | "false" | "1" | undefined; syntheticPresenceLeaveAfter?: string | undefined; closeInactiveWebSocketAfter?: string | undefined; schemaVersion?: SchemaVersion | undefined; includeRawBytes?: "0" | "true" | "false" | "1" | undefined; useEnterpriseApi?: "0" | "true" | "false" | "1" | undefined; webcastPlatform?: "mobile" | "web" | undefined; } | undefined; sessionId?: string | null | undefined; ttTargetIdc?: string | null | undefined; }>; type ParsedWebSocketOptions = Omit<z.infer<typeof WebSocketOptionsSchema>, 'features'> & { features: WebSocketFeatureFlagsType; }; type WebSocketFeatureFlagsType = z.infer<typeof WebSocketFeatureFlags>; type WebSocketOptions = Omit<ParsedWebSocketOptions, 'features'> & { features?: Partial<WebSocketFeatureFlagsType>; }; type ClientMessageBundle = { timestamp: number; messages: DecodedData[]; }; //#endregion //#region src/client/utilities.d.ts declare const BASE_URL: string; /** * Creates a WebSocket URL with query parameters based on the provided context. * * @param options {WebSocketOptions} The context to include in the WebSocket URL */ declare function createWebSocketUrl(options: WebSocketOptions): string; declare function normalizeUniqueId(uniqueId: string): string; //#endregion //#region src/webcast/proto-utils.d.ts /** FUNCTION: Extract type T from MessageFns<T> **/ type ExtractType<T> = T extends MessageFns<infer U> ? U : never; /** FUNCTION: Extract only those message types that have a 'common' property **/ type FilterMessagesWithCommon<T> = { [K in keyof T]: T[K] extends { common: any; } ? K : never }[keyof T]; /** MAP: Property names in tiktokSchema file to types **/ type TikTokSchema = typeof tiktokSchema; /** MAP: Property names to T from ExtractType<T>. Includes 'nevers' that need to be filtered out **/ type RawExtractedTypes = { [K in keyof TikTokSchema]: ExtractType<TikTokSchema[K]> }; /** UNION: All keys in RawExtractedTypes that DON'T result in a "never" **/ type FilteredKeys = { [K in keyof RawExtractedTypes]: RawExtractedTypes[K] extends never ? never : K }[keyof RawExtractedTypes]; /** MAP: Message names to the interface they decode into. In tiktok-live-proto the * codec const and the interface share the same identifier, so the message name * IS the schema key — no suffix-stripping needed. **/ type WebcastMessageMap = { [K in FilteredKeys]: RawExtractedTypes[K] }; /** UNION: All message values (i.e. the Protobuf Message interfaces) **/ type WebcastMessage = WebcastMessageMap[keyof WebcastMessageMap]; /** UNION: All message names **/ type WebcastMessageName = keyof WebcastMessageMap; /** MAP: Only those messages with a 'common' property **/ type WebcastEventMap = { [K in FilterMessagesWithCommon<WebcastMessageMap>]: WebcastMessageMap[K] }; /** UNION: Names of ONLY Event messages (i.e. Top-Level messages) **/ type WebcastEventName = keyof WebcastEventMap; /** UNION: Values of ONLY Event messages **/ type WebcastEvent = WebcastEventMap[keyof WebcastEventMap]; type RoomInfoEvent = { type: 'roomInfo'; data: Record<string, any>; }; type SuperFanEvent = { type: 'superFan'; data: WebcastBarrageMessage; }; type DecodeErrorEvent = { type: 'decodeError'; data: { message: string; }; }; type TikTokConnectEvent = { type: 'tiktok.connect'; data: { agentId: string; }; }; type TikTokDisconnectEvent = { type: 'tiktok.disconnect'; data: { reason: ClientCloseCode; }; }; type TikTokRawBytes = { type: 'tiktok.rawBytes'; data: { raw: string; }; }; type WorkerInfoEvent = { type: 'workerInfo'; data: { webSocketId: string; schemaVersion: SchemaVersion; features: WebSocketFeatureFlagsType; isLoggedIn: boolean; }; }; type PresenceRecord = { user: Pick<User, 'userId' | 'uniqueId' | 'nickname' | "profilePicture">; firstSeen: number; lastSeen: number; }; type PresenceRegistry = Record<string, PresenceRecord>; type SyntheticLeaveMessage = { type: 'SyntheticLeaveMessage'; data: PresenceRecord; }; type SyntheticJoinMessage = { type: 'SyntheticJoinMessage'; data: PresenceRecord; }; type CustomData = RoomInfoEvent | WorkerInfoEvent | SyntheticJoinMessage | SyntheticLeaveMessage | TikTokConnectEvent | TikTokDisconnectEvent | SuperFanEvent | DecodeErrorEvent | TikTokRawBytes; /** UNION: All possible pairs of type to the data the type represents **/ type DecodedData = { [K in WebcastMessageName]: { type: K; data: WebcastMessageMap[K]; } }[WebcastMessageName] | CustomData; type DecodedWebcastPushFrame = WebcastPushFrame & { protoMessageFetchResult?: ProtoMessageFetchResult; }; type RequiredDecodedWebcastPushFrame = Omit<DecodedWebcastPushFrame, 'protoMessageFetchResult'> & { protoMessageFetchResult: ProtoMessageFetchResult; }; //#endregion //#region src/webcast/schemas.d.ts declare enum SchemaVersion { v1 = "v1", v2 = "v2" } declare const WebcastSchemas: { v1: typeof tikTokSchemaV1; v2: typeof tiktokSchema; }; //#endregion //#region src/extras/typed-emitter.d.ts /** An event-map type for the typed-emitter **/ type TypedEmitterEventMap = { [K in WebcastEventName]: (event: WebcastMessageMap[K]) => void }; declare class WebcastEventEmitter extends EventEmitter<TypedEmitterEventMap> {} declare namespace index_d_exports { export { BASE_URL, ClientCloseCode, ClientMessageBundle, CloseMessageMap, CustomData, DecodeErrorEvent, DecodedData, DecodedWebcastPushFrame, ParsedWebSocketOptions, PresenceRecord, PresenceRegistry, RequiredDecodedWebcastPushFrame, RoomInfoEvent, SchemaVersion, SuperFanEvent, SyntheticJoinMessage, SyntheticLeaveMessage, TikTokConnectEvent, TikTokDisconnectEvent, TikTokRawBytes, TypedEmitterEventMap, WebSocketFeatureFlags, WebSocketFeatureFlagsType, WebSocketOptions, WebSocketOptionsSchema, WebcastEvent, WebcastEventEmitter, WebcastEventMap, WebcastEventName, WebcastMessage, WebcastMessageMap, WebcastMessageName, WebcastSchemas, WorkerInfoEvent, createWebSocketUrl, normalizeUniqueId }; } //#endregion export { BASE_URL, ClientCloseCode, ClientMessageBundle, CloseMessageMap, CustomData, DecodeErrorEvent, DecodedData, DecodedWebcastPushFrame, ParsedWebSocketOptions, PresenceRecord, PresenceRegistry, RequiredDecodedWebcastPushFrame, RoomInfoEvent, SchemaVersion, SuperFanEvent, SyntheticJoinMessage, SyntheticLeaveMessage, TikTokConnectEvent, TikTokDisconnectEvent, TikTokRawBytes, TypedEmitterEventMap, WebSocketFeatureFlags, WebSocketFeatureFlagsType, WebSocketOptions, WebSocketOptionsSchema, WebcastEvent, WebcastEventEmitter, WebcastEventMap, WebcastEventName, WebcastMessage, WebcastMessageMap, WebcastMessageName, WebcastSchemas, WorkerInfoEvent, createWebSocketUrl, normalizeUniqueId };