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