Initial source upload
commit
7a00f2471d
|
@ -0,0 +1,184 @@
|
||||||
|
import {
|
||||||
|
ClientSessionType,
|
||||||
|
CommandSessionInitializeAgent,
|
||||||
|
CommandSessionUpdateLocale,
|
||||||
|
MessageCommand,
|
||||||
|
MessageCommandResult,
|
||||||
|
NotifyClientsOnline
|
||||||
|
} from "./Messages";
|
||||||
|
import {geoLocationProvider} from "./GeoLocation";
|
||||||
|
import {clientServiceLogger} from "./Logging";
|
||||||
|
import {ClientServiceConnection} from "./Connection";
|
||||||
|
|
||||||
|
export type LocalAgent = {
|
||||||
|
clientVersion: string,
|
||||||
|
uiVersion: string,
|
||||||
|
|
||||||
|
architecture: string,
|
||||||
|
platform: string,
|
||||||
|
platformVersion: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientServiceConfig {
|
||||||
|
getSelectedLocaleUrl() : string | null;
|
||||||
|
getSessionType() : ClientSessionType;
|
||||||
|
generateHostInfo() : LocalAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClientServices {
|
||||||
|
readonly config: ClientServiceConfig;
|
||||||
|
private connection: ClientServiceConnection;
|
||||||
|
|
||||||
|
private sessionInitialized: boolean;
|
||||||
|
private retryTimer: any;
|
||||||
|
|
||||||
|
private initializeAgentId: number;
|
||||||
|
private initializeLocaleId: number;
|
||||||
|
|
||||||
|
constructor(config: ClientServiceConfig) {
|
||||||
|
this.config = config;
|
||||||
|
this.initializeAgentId = 0;
|
||||||
|
this.initializeLocaleId = 0;
|
||||||
|
|
||||||
|
this.sessionInitialized = false;
|
||||||
|
this.connection = new ClientServiceConnection(5000);
|
||||||
|
this.connection.events.on("notify_state_changed", event => {
|
||||||
|
if(event.newState !== "connected") {
|
||||||
|
this.sessionInitialized = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientServiceLogger.logInfo("Connected successfully. Initializing session.");
|
||||||
|
this.executeCommandWithRetry({ type: "SessionInitialize", payload: { anonymize_ip: false }}, 2500).then(result => {
|
||||||
|
if(result.type !== "Success") {
|
||||||
|
if(result.type === "ConnectionClosed") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientServiceLogger.logError( "Failed to initialize session. Retrying in 120 seconds. Result: %o", result);
|
||||||
|
this.scheduleRetry(120 * 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendInitializeAgent().then(undefined);
|
||||||
|
this.sendLocaleUpdate().then(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.events.on("notify_notify_received", event => {
|
||||||
|
switch (event.notify.type) {
|
||||||
|
case "NotifyClientsOnline":
|
||||||
|
this.handleNotifyClientsOnline(event.notify.payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.connection.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.connection.disconnect();
|
||||||
|
clearTimeout(this.retryTimer);
|
||||||
|
|
||||||
|
this.initializeAgentId++;
|
||||||
|
this.initializeLocaleId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleRetry(time: number) {
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
this.retryTimer = setTimeout(() => this.connection.connect(), time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns as soon the result indicates that something else went wrong rather than transmitting.
|
||||||
|
* @param command
|
||||||
|
* @param retryInterval
|
||||||
|
*/
|
||||||
|
private async executeCommandWithRetry(command: MessageCommand, retryInterval: number) : Promise<MessageCommandResult> {
|
||||||
|
while(true) {
|
||||||
|
const result = await this.connection.executeCommand(command);
|
||||||
|
switch (result.type) {
|
||||||
|
case "ServerInternalError":
|
||||||
|
case "CommandEnqueueError":
|
||||||
|
case "ClientSessionUninitialized":
|
||||||
|
const shouldRetry = await new Promise<boolean>(resolve => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
listener();
|
||||||
|
resolve(true);
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
const listener = this.connection.events.on("notify_state_changed", event => {
|
||||||
|
if(event.newState !== "connected") {
|
||||||
|
resolve(false);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if(shouldRetry) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendInitializeAgent() {
|
||||||
|
const taskId = ++this.initializeAgentId;
|
||||||
|
|
||||||
|
const hostInfo = this.config.generateHostInfo();
|
||||||
|
const payload: CommandSessionInitializeAgent = {
|
||||||
|
session_type: this.config.getSessionType(),
|
||||||
|
architecture: hostInfo.architecture,
|
||||||
|
platform_version: hostInfo.platformVersion,
|
||||||
|
platform: hostInfo.platform,
|
||||||
|
client_version: hostInfo.clientVersion,
|
||||||
|
ui_version: hostInfo.uiVersion
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.initializeAgentId !== taskId) {
|
||||||
|
/* We don't want to send that stuff any more */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.executeCommandWithRetry({ type: "SessionInitializeAgent", payload }, 2500).then(result => {
|
||||||
|
clientServiceLogger.logTrace("Agent initialize result: %o", result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendLocaleUpdate() {
|
||||||
|
const taskId = ++this.initializeLocaleId;
|
||||||
|
|
||||||
|
const payload: CommandSessionUpdateLocale = {
|
||||||
|
ip_country: null,
|
||||||
|
selected_locale: null,
|
||||||
|
local_timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
const geoInfo = await geoLocationProvider.queryInfo(2500);
|
||||||
|
payload.ip_country = geoInfo?.country?.toLowerCase() || null;
|
||||||
|
payload.selected_locale = this.config.getSelectedLocaleUrl();
|
||||||
|
|
||||||
|
if(this.initializeLocaleId !== taskId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.executeCommand({ type: "SessionUpdateLocale", payload }).then(result => {
|
||||||
|
clientServiceLogger.logTrace("Agent local update result: %o", result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleNotifyClientsOnline(notify: NotifyClientsOnline) {
|
||||||
|
clientServiceLogger.logInfo("Received user count update: %o", notify);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
import {clientServiceLogger} from "./Logging";
|
||||||
|
import {Message, MessageCommand, MessageCommandResult, MessageNotify} from "./Messages";
|
||||||
|
import {Registry} from "tc-events";
|
||||||
|
|
||||||
|
const kApiVersion = 1;
|
||||||
|
|
||||||
|
type ConnectionState = "disconnected" | "connecting" | "connected" | "reconnect-pending";
|
||||||
|
type PendingCommand = {
|
||||||
|
resolve: (result: MessageCommandResult) => void,
|
||||||
|
timeout: any
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ClientServiceConnectionEvents {
|
||||||
|
notify_state_changed: { oldState: ConnectionState, newState: ConnectionState },
|
||||||
|
notify_notify_received: { notify: MessageNotify }
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenIndex = 0;
|
||||||
|
export class ClientServiceConnection {
|
||||||
|
readonly events: Registry<ClientServiceConnectionEvents>;
|
||||||
|
readonly reconnectInterval: number;
|
||||||
|
|
||||||
|
private reconnectTimeout: any;
|
||||||
|
private connectionState: ConnectionState;
|
||||||
|
private connection: WebSocket;
|
||||||
|
|
||||||
|
private pendingCommands: {[key: string]: PendingCommand} = {};
|
||||||
|
|
||||||
|
constructor(reconnectInterval: number) {
|
||||||
|
this.events = new Registry<ClientServiceConnectionEvents>();
|
||||||
|
this.reconnectInterval = reconnectInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.disconnect();
|
||||||
|
this.events.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
getState() : ConnectionState {
|
||||||
|
return this.connectionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setState(newState: ConnectionState) {
|
||||||
|
if(this.connectionState === newState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldState = this.connectionState;
|
||||||
|
this.connectionState = newState;
|
||||||
|
this.events.fire("notify_state_changed", { oldState, newState })
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.disconnect();
|
||||||
|
|
||||||
|
this.setState("connecting");
|
||||||
|
|
||||||
|
let address;
|
||||||
|
address = "client-services.teaspeak.de:27791";
|
||||||
|
address = "localhost:1244";
|
||||||
|
//address = "192.168.40.135:1244";
|
||||||
|
|
||||||
|
this.connection = new WebSocket(`wss://${address}/ws-api/v${kApiVersion}`);
|
||||||
|
this.connection.onclose = event => {
|
||||||
|
clientServiceLogger.logTrace("Lost connection to statistics server (Connection closed). Reason: %s", event.reason ? `${event.reason} (${event.code})` : event.code);
|
||||||
|
this.handleConnectionLost();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.connection.onopen = () => {
|
||||||
|
clientServiceLogger.logTrace("Connection established.");
|
||||||
|
this.setState("connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.onerror = () => {
|
||||||
|
if(this.connectionState === "connecting") {
|
||||||
|
clientServiceLogger.logTrace("Failed to connect to target server.");
|
||||||
|
this.handleConnectFail();
|
||||||
|
} else {
|
||||||
|
clientServiceLogger.logTrace("Received web socket error which indicates that the connection has been closed.");
|
||||||
|
this.handleConnectionLost();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.connection.onmessage = event => {
|
||||||
|
if(typeof event.data !== "string") {
|
||||||
|
clientServiceLogger.logTrace("Receved non text message: %o", event.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleServerMessage(event.data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if(this.connection) {
|
||||||
|
this.connection.onclose = undefined;
|
||||||
|
this.connection.onopen = undefined;
|
||||||
|
this.connection.onmessage = undefined;
|
||||||
|
this.connection.onerror = undefined;
|
||||||
|
|
||||||
|
this.connection.close();
|
||||||
|
this.connection = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const command of Object.values(this.pendingCommands)) {
|
||||||
|
command.resolve({ type: "ConnectionClosed" });
|
||||||
|
}
|
||||||
|
this.pendingCommands = {};
|
||||||
|
|
||||||
|
clearTimeout(this.reconnectTimeout);
|
||||||
|
this.reconnectTimeout = undefined;
|
||||||
|
|
||||||
|
this.setState("disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelReconnect() {
|
||||||
|
clearTimeout(this.reconnectTimeout);
|
||||||
|
this.reconnectTimeout = undefined;
|
||||||
|
|
||||||
|
if(this.connectionState === "reconnect-pending") {
|
||||||
|
this.setState("disconnected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeCommand(command: MessageCommand) : Promise<MessageCommandResult> {
|
||||||
|
if(this.connectionState !== "connected") {
|
||||||
|
return { type: "ConnectionClosed" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = "tk-" + ++tokenIndex;
|
||||||
|
try {
|
||||||
|
this.connection.send(JSON.stringify({
|
||||||
|
type: "Command",
|
||||||
|
token: token,
|
||||||
|
command: command
|
||||||
|
} as Message));
|
||||||
|
} catch (error) {
|
||||||
|
clientServiceLogger.logTrace("Failed to send command: %o", error);
|
||||||
|
return { type: "GenericError", error: "Failed to send command" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return await new Promise(resolve => {
|
||||||
|
const proxiedResolve = (result: MessageCommandResult) => {
|
||||||
|
clearTimeout(this.pendingCommands[token]?.timeout);
|
||||||
|
delete this.pendingCommands[token];
|
||||||
|
resolve(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pendingCommands[token] = {
|
||||||
|
resolve: proxiedResolve,
|
||||||
|
timeout: setTimeout(() => proxiedResolve({ type: "ConnectionTimeout" }), 5000)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleConnectFail() {
|
||||||
|
this.disconnect();
|
||||||
|
this.executeReconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleConnectionLost() {
|
||||||
|
this.disconnect();
|
||||||
|
this.executeReconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private executeReconnect() {
|
||||||
|
if(!this.reconnectInterval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientServiceLogger.logTrace("Scheduling reconnect in %dms", this.reconnectInterval);
|
||||||
|
this.reconnectTimeout = setTimeout(() => this.connect(), this.reconnectInterval);
|
||||||
|
this.setState("reconnect-pending");
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleServerMessage(message: string) {
|
||||||
|
let data: Message;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(message);
|
||||||
|
} catch (_error) {
|
||||||
|
clientServiceLogger.logTrace("Received message which isn't parsable as JSON.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data.type === "Command") {
|
||||||
|
clientServiceLogger.logTrace("Received message of type command. The server should not send these. Message: %o", data);
|
||||||
|
/* Well this is odd. We should never receive such */
|
||||||
|
} else if(data.type === "CommandResult") {
|
||||||
|
if(data.token === null) {
|
||||||
|
clientServiceLogger.logTrace("Received general error: %o", data.result);
|
||||||
|
} else if(this.pendingCommands[data.token]) {
|
||||||
|
/* The entry itself will be cleaned up by the resolve callback */
|
||||||
|
this.pendingCommands[data.token].resolve(data.result);
|
||||||
|
} else {
|
||||||
|
clientServiceLogger.logWarn("Received command result for unknown token: %o", data.token);
|
||||||
|
}
|
||||||
|
} else if(data.type === "Notify") {
|
||||||
|
this.events.fire("notify_notify_received", { notify: data.notify });
|
||||||
|
} else {
|
||||||
|
clientServiceLogger.logWarn("Received message with invalid type: %o", (data as any).type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
import jsonp from 'simple-jsonp-promise';
|
||||||
|
import {clientServiceLogger} from "./Logging";
|
||||||
|
|
||||||
|
interface GeoLocationInfo {
|
||||||
|
/* The country code */
|
||||||
|
country: string,
|
||||||
|
|
||||||
|
city?: string,
|
||||||
|
region?: string,
|
||||||
|
timezone?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GeoLocationResolver {
|
||||||
|
name() : string;
|
||||||
|
resolve() : Promise<GeoLocationInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const kLocalCacheKey = "geo-info";
|
||||||
|
type GeoLocationCache = {
|
||||||
|
version: 1,
|
||||||
|
|
||||||
|
timestamp: number,
|
||||||
|
info: GeoLocationInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
class GeoLocationProvider {
|
||||||
|
private readonly resolver: GeoLocationResolver[];
|
||||||
|
private currentResolverIndex: number;
|
||||||
|
|
||||||
|
private cachedInfo: GeoLocationInfo | undefined;
|
||||||
|
private lookupPromise: Promise<GeoLocationInfo>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.resolver = [
|
||||||
|
new GeoResolverIpInfo(),
|
||||||
|
new GeoResolverIpData()
|
||||||
|
];
|
||||||
|
this.currentResolverIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCache() {
|
||||||
|
this.doLoadCache();
|
||||||
|
if(!this.cachedInfo) {
|
||||||
|
this.lookupPromise = this.doQueryInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private doLoadCache() : GeoLocationInfo {
|
||||||
|
try {
|
||||||
|
const rawItem = localStorage.getItem(kLocalCacheKey);
|
||||||
|
if(!rawItem) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info: GeoLocationCache = JSON.parse(rawItem);
|
||||||
|
if(info.version !== 1) {
|
||||||
|
throw tr("invalid version number");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(info.timestamp + 2 * 24 * 60 * 60 * 1000 < Date.now()) {
|
||||||
|
throw tr("cache is too old");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(info.timestamp + 12 * 60 * 60 * 1000 > Date.now()) {
|
||||||
|
clientServiceLogger.logTrace(tr("Geo cache is less than 12hrs old. Don't updating."));
|
||||||
|
this.lookupPromise = Promise.resolve(info.info);
|
||||||
|
} else {
|
||||||
|
this.lookupPromise = this.doQueryInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedInfo = info.info;
|
||||||
|
} catch (error) {
|
||||||
|
clientServiceLogger.logTrace(tr("Failed to load geo resolve cache: %o"), error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryInfo(timeout: number) : Promise<GeoLocationInfo | undefined> {
|
||||||
|
return await new Promise<GeoLocationInfo>(resolve => {
|
||||||
|
if(!this.lookupPromise) {
|
||||||
|
resolve(this.cachedInfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutId: any = typeof timeout === "number" ? setTimeout(() => resolve(this.cachedInfo), timeout) : -1;
|
||||||
|
this.lookupPromise.then(result => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async doQueryInfo() : Promise<GeoLocationInfo | undefined> {
|
||||||
|
while(this.currentResolverIndex < this.resolver.length) {
|
||||||
|
const resolver = this.resolver[this.currentResolverIndex++];
|
||||||
|
try {
|
||||||
|
const info = await resolver.resolve();
|
||||||
|
clientServiceLogger.logTrace(tr("Successfully resolved geo info from %s: %o"), resolver.name(), info);
|
||||||
|
|
||||||
|
localStorage.setItem(kLocalCacheKey, JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
info: info
|
||||||
|
} as GeoLocationCache));
|
||||||
|
return info;
|
||||||
|
} catch (error) {
|
||||||
|
clientServiceLogger.logTrace(tr("Geo resolver %s failed: %o. Trying next one."), resolver.name(), error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientServiceLogger.logTrace(tr("All geo resolver failed."));
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GeoResolverIpData implements GeoLocationResolver {
|
||||||
|
name(): string {
|
||||||
|
return "ipdata.co";
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve(): Promise<GeoLocationInfo> {
|
||||||
|
const response = await fetch("https://api.ipdata.co/?api-key=test");
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
if(!("country_code" in json)) {
|
||||||
|
throw tr("missing country code");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
country: json["country_code"],
|
||||||
|
region: json["region"],
|
||||||
|
city: json["city"],
|
||||||
|
timezone: json["time_zone"]["name"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class GeoResolverIpInfo implements GeoLocationResolver {
|
||||||
|
name(): string {
|
||||||
|
return "ipinfo.io";
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve(): Promise<GeoLocationInfo> {
|
||||||
|
const response = await jsonp("https://ipinfo.io");
|
||||||
|
if(!("country" in response)) {
|
||||||
|
throw tr("missing country");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
country: response["country"],
|
||||||
|
|
||||||
|
city: response["city"],
|
||||||
|
region: response["region"],
|
||||||
|
timezone: response["timezone"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export let geoLocationProvider: GeoLocationProvider;
|
||||||
|
|
||||||
|
geoLocationProvider = new GeoLocationProvider();
|
||||||
|
geoLocationProvider.loadCache();
|
|
@ -0,0 +1,39 @@
|
||||||
|
export interface ClientServiceLogger {
|
||||||
|
logTrace(message: string, ...args: any[]);
|
||||||
|
logDebug(message: string, ...args: any[]);
|
||||||
|
logInfo(message: string, ...args: any[]);
|
||||||
|
logWarn(message: string, ...args: any[]);
|
||||||
|
logError(message: string, ...args: any[]);
|
||||||
|
logCritical(message: string, ...args: any[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export let clientServiceLogger: ClientServiceLogger;
|
||||||
|
clientServiceLogger = new class implements ClientServiceLogger {
|
||||||
|
logCritical(message: string, ...args: any[]) {
|
||||||
|
console.error("[Critical] " + message, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
logError(message: string, ...args: any[]) {
|
||||||
|
console.error("[Error] " + message, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
logWarn(message: string, ...args: any[]) {
|
||||||
|
console.warn("[Warn ] " + message, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo(message: string, ...args: any[]) {
|
||||||
|
console.info("[Info ] " + message, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug(message: string, ...args: any[]) {
|
||||||
|
console.debug("[Debug] " + message, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
logTrace(message: string, ...args: any[]) {
|
||||||
|
console.debug("[Trace] " + message, ...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setClientServiceLogger(logger: ClientServiceLogger) {
|
||||||
|
clientServiceLogger = logger;
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* Basic message declarations */
|
||||||
|
export type Message =
|
||||||
|
| { type: "Command"; token: string; command: MessageCommand }
|
||||||
|
| { type: "CommandResult"; token: string | null; result: MessageCommandResult }
|
||||||
|
| { type: "Notify"; notify: MessageNotify };
|
||||||
|
|
||||||
|
export type MessageCommand =
|
||||||
|
| { type: "SessionInitialize"; payload: CommandSessionInitialize }
|
||||||
|
| { type: "SessionInitializeAgent"; payload: CommandSessionInitializeAgent }
|
||||||
|
| { type: "SessionUpdateLocale"; payload: CommandSessionUpdateLocale }
|
||||||
|
| { type: "InviteQueryInfo"; payload: CommandInviteQueryInfo }
|
||||||
|
| { type: "InviteLogAction"; payload: CommandInviteLogAction }
|
||||||
|
| { type: "InviteCreate"; payload: CommandInviteCreate };
|
||||||
|
|
||||||
|
export type MessageCommandResult =
|
||||||
|
| { type: "Success" }
|
||||||
|
| { type: "GenericError"; error: string }
|
||||||
|
| { type: "ConnectionTimeout" }
|
||||||
|
| { type: "ConnectionClosed" }
|
||||||
|
| { type: "ClientSessionUninitialized" }
|
||||||
|
| { type: "ServerInternalError" }
|
||||||
|
| { type: "ParameterInvalid"; parameter: string }
|
||||||
|
| { type: "CommandParseError"; error: string }
|
||||||
|
| { type: "CommandEnqueueError"; fields: string }
|
||||||
|
| { type: "CommandNotFound" }
|
||||||
|
| { type: "CommandNotImplemented" }
|
||||||
|
| { type: "SessionAlreadyInitialized" }
|
||||||
|
| { type: "SessionAgentAlreadyInitialized" }
|
||||||
|
| { type: "SessionNotInitialized" }
|
||||||
|
| { type: "SessionAgentNotInitialized" }
|
||||||
|
| { type: "SessionInvalidType" }
|
||||||
|
| { type: "InviteSessionNotInitialized" }
|
||||||
|
| { type: "InviteSessionAlreadyInitialized" }
|
||||||
|
| { type: "InviteKeyInvalid"; fields: string }
|
||||||
|
| { type: "InviteKeyNotFound" };
|
||||||
|
|
||||||
|
export type MessageNotify =
|
||||||
|
| { type: "NotifyClientsOnline"; payload: NotifyClientsOnline }
|
||||||
|
| { type: "NotifyInviteCreated"; payload: NotifyInviteCreated }
|
||||||
|
| { type: "NotifyInviteInfo"; payload: NotifyInviteInfo };
|
||||||
|
|
||||||
|
/* Some command data payload */
|
||||||
|
export enum ClientSessionType {
|
||||||
|
WebClient = 0,
|
||||||
|
TeaClient = 1,
|
||||||
|
InviteWebSite = 16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All commands */
|
||||||
|
export type CommandSessionInitialize = { anonymize_ip: boolean };
|
||||||
|
|
||||||
|
export type CommandSessionInitializeAgent = { session_type: ClientSessionType; platform: string | null; platform_version: string | null; architecture: string | null; client_version: string | null; ui_version: string | null };
|
||||||
|
|
||||||
|
export type CommandSessionUpdateLocale = { ip_country: string | null; selected_locale: string | null; local_timestamp: number };
|
||||||
|
|
||||||
|
export type CommandInviteQueryInfo = { link_id: string };
|
||||||
|
|
||||||
|
export type CommandInviteLogAction = { click_type: number };
|
||||||
|
|
||||||
|
export type CommandInviteCreate = { new_link: boolean; properties_connect: { [key: string]: string }; properties_info: { [key: string]: string } };
|
||||||
|
|
||||||
|
/* Notifies */
|
||||||
|
export type NotifyClientsOnline = { users_online: { [key: number]: number }; unique_users_online: { [key: number]: number }; total_users_online: number; total_unique_users_online: number };
|
||||||
|
|
||||||
|
export type NotifyInviteCreated = { link_id: string; admin_token: string | null };
|
||||||
|
|
||||||
|
export type NotifyInviteInfo = { link_id: string; timestamp_created: number; timestamp_deleted: number; amount_viewed: number; amount_clicked: number; properties_connect: { [key: string]: string }; properties_info: { [key: string]: string } };
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { ClientServiceConfig, ClientServices, LocalAgent } from "./ClientService";
|
||||||
|
export { ClientServiceLogger, setClientServiceLogger } from "./Logging";
|
||||||
|
export { ClientSessionType } from "./Messages";
|
Loading…
Reference in New Issue