6 Commits
v1.1.1 ... main

13 changed files with 1732 additions and 1006 deletions

View File

@@ -1,18 +1,18 @@
# React TeamSpeak5 RemoteApp API # React TeamSpeak5/6 RemoteApp API
[![npm](https://img.shields.io/npm/v/react-ts5-remote-app-api.svg)](https://www.npmjs.com/package/react-ts5-remote-app-api) ![downloads](https://img.shields.io/npm/dt/react-ts5-remote-app-api.svg) [![npm](https://img.shields.io/npm/v/react-teamspeak-remote-app-api.svg)](https://www.npmjs.com/package/react-teamspeak-remote-app-api) ![downloads](https://img.shields.io/npm/dt/react-teamspeak-remote-app-api.svg)
This is a ReactJS hook for the TeamSpeak5 RemoteApp API. This is a ReactJS hook for the TeamSpeak5/6 RemoteApp API.
It gathers all the events and methods from the API and makes them available as React states. It gathers all the events and methods from the API and makes them available as React states.
Please note that this is still a work in progress and not all events and methods are implemented yet. Please note that this is still a work in progress and not all events and methods are implemented yet.
Projects which are using this hook: [TS5 OBS Overlay](https://github.com/DerTyp7/ts5-obs-overlay) Projects which are using this hook: [TeamSpeak OBS Overlay](https://github.com/DerTyp7/teamspeak-obs-overlay)
## Table of Contents ## Table of Contents
- [React TeamSpeak5 RemoteApp API](#react-teamspeak5-remoteapp-api) - [React TeamSpeak5/6 RemoteApp API](#react-teamspeak56-remoteapp-api)
- [Table of Contents](#table-of-contents) - [Table of Contents](#table-of-contents)
- [Installation](#installation) - [Installation](#installation)
- [Usage](#usage) - [Usage](#usage)
@@ -26,13 +26,13 @@ Projects which are using this hook: [TS5 OBS Overlay](https://github.com/DerTyp7
## Installation ## Installation
```bash ```bash
npm install react-ts5-remote-app-api npm install react-teamspeak-remote-app-api
``` ```
## Usage ## Usage
```Typescript ```Typescript
import useTSRemoteApp, { IClient } from "react-ts5-remote-app-api"; import useTSRemoteApp, { IClient } from "react-teamspeak-remote-app-api";
export default function App() { export default function App() {
const { const {
@@ -57,7 +57,7 @@ export default function App() {
### Get all clients in the current channel ### Get all clients in the current channel
```Typescript ```Typescript
import useTSRemoteApp, { IClient } from "react-ts5-remote-app-api"; import useTSRemoteApp, { IClient } from "react-teamspeak-remote-app-api";
default function App() { default function App() {
const { clientsInChannel } = useTSRemoteApp({ const { clientsInChannel } = useTSRemoteApp({

View File

@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

2539
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,17 @@
{ {
"name": "react-ts5-remote-app-api", "name": "react-teamspeak-remote-app-api",
"version": "1.1.1", "version": "2.0.0",
"description": "React hook/api for the TeamSpeak5 remote app feature", "description": "React hook/api for the TeamSpeak5/6 remote app feature",
"main": "dist/cjs/index.js", "main": "dist/cjs/index.js",
"module": "dist/esm/index.js", "module": "dist/esm/index.js",
"source": "src/index.ts", "source": "src/index.ts",
"types": "dist/cjs/index.d.ts", "types": "dist/cjs/index.d.ts",
"scripts": { "scripts": {
"build": "npm run build:esm && npm run build:js", "build": "npm run build:esm && npm run build:js",
"build:esm": "tsc --project tsconfig.build.json --outDir dist/esm --module esnext", "build:esm": "tsc --project tsconfig.build.json --outDir dist/esm --module esnext && npm run alias:esm",
"build:js": "tsc --project tsconfig.build.json --outDir dist/cjs --module commonjs" "build:js": "tsc --project tsconfig.build.json --outDir dist/cjs --module commonjs && npm run alias:js",
"alias:esm": "tsc-alias --project tsconfig.build.json --outDir dist/esm",
"alias:js": "tsc-alias --project tsconfig.build.json --outDir dist/cjs"
}, },
"keywords": [ "keywords": [
"TeamSpeak5", "TeamSpeak5",
@@ -25,13 +27,13 @@
"author": "DerTyp7", "author": "DerTyp7",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/DerTyp7/react-ts5-remote-app-api" "url": "https://github.com/DerTyp7/react-teamspeak-remote-app-api"
}, },
"license": "ISC", "license": "ISC",
"peerDependencies": { "peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react": "^18.0.0 || ^19.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" "react-dom": "^18.0.0 || ^19.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@types/react": { "@types/react": {
@@ -43,22 +45,24 @@
"src" "src"
], ],
"dependencies": { "dependencies": {
"@rollup/plugin-typescript": "^11.1.2", "@rollup/plugin-typescript": "^12.1.2",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0", "@types/node": "^22.10.10",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^18.0.0 || ^19.0.0",
"rollup-plugin-typescript-paths": "^1.4.0", "react-dom": "^18.0.0 || ^19.0.0",
"ts-node": "^10.9.1", "rollup-plugin-typescript-paths": "^1.5.0",
"tslib": "^2.6.0" "ts-node": "^10.9.2",
"tslib": "^2.8.1"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.15", "@types/react": "^19.0.8",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^19.0.3",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^8.21.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^8.21.0",
"@vitejs/plugin-react-swc": "^3.3.2", "@vitejs/plugin-react-swc": "^3.7.2",
"eslint": "^8.45.0", "eslint": "^9.18.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.3", "eslint-plugin-react-refresh": "^0.4.18",
"typescript": "^5.0.2" "tsc-alias": "^1.8.10",
"typescript": "^5.7.3"
} }
} }

View File

@@ -1,8 +1,8 @@
import { IAuthSenderPayload, IChannel, IClient, IConnection, ITS5ConnectionHandler, ITS5DataHandler, ITS5MessageHandler } from "../../interfaces/teamspeak"; import { IAuthSenderPayload, IChannel, IClient, IConnection, ITS5ConnectionHandler, ITS5DataHandler, ITS5MessageHandler } from "interfaces/teamspeak";
import { TS5DataHandler } from "./dataHandler"; import { TS5DataHandler } from "handlers/teamspeak/dataHandler";
import { TS5MessageHandler } from "./messageHandler"; import { TS5MessageHandler } from "handlers/teamspeak/messageHandler";
import { ILogger } from "../../utils/logger"; import { ILogger } from "utils/logger";
import { ITSRemoteAppAuthPayloadOptions } from "../../interfaces/api"; import { ITSRemoteAppAuthPayloadOptions } from "interfaces/api";
// Establish connection to TS5 client // Establish connection to TS5 client
@@ -56,7 +56,7 @@ export class TS5ConnectionHandler implements ITS5ConnectionHandler {
this.logger.log('Connecting to TS5 client...'); this.logger.log('Connecting to TS5 client...');
// Create authentication payload // Create authentication payload
const initalPayload: IAuthSenderPayload = { const initialPayload: IAuthSenderPayload = {
type: "auth", type: "auth",
payload: { payload: {
...this.authPayload, ...this.authPayload,
@@ -68,15 +68,15 @@ export class TS5ConnectionHandler implements ITS5ConnectionHandler {
this.ws.onopen = () => { this.ws.onopen = () => {
// Send authentication payload to TS5 client // Send authentication payload to TS5 client
this.ws.send(JSON.stringify(initalPayload)); this.ws.send(JSON.stringify(initialPayload));
this.logger.wsSent(initalPayload); this.logger.wsSent(initialPayload);
}; };
this.ws.onclose = (event) => { this.ws.onclose = (event) => {
this.logger.log("WebSocket connection closed", event); this.logger.log("WebSocket connection closed", event);
// If the connection was closed before authentication, remove the API key from local storage // If the connection was closed before authentication, remove the API key from local storage
// OBS weirdly caches the localstorage and is very stubborn about clearing it (even when clicken "Clear Cache") // OBS weirdly caches the localstorage and is very stubborn about clearing it (even when clicking "Clear Cache")
if (!this.authenticated) { if (!this.authenticated) {
this.logger.log("WebSocket connection closed before authentication"); this.logger.log("WebSocket connection closed before authentication");
localStorage.removeItem("apiKey"); localStorage.removeItem("apiKey");
@@ -120,6 +120,9 @@ export class TS5ConnectionHandler implements ITS5ConnectionHandler {
case "channels": case "channels":
this.messageHandler.handleChannelsMessage(data); this.messageHandler.handleChannelsMessage(data);
break; break;
case "channelCreated":
this.messageHandler.handleChannelCreatedMessage(data);
break;
default: default:
this.logger.log(`No handler for event type: ${data.type}`); this.logger.log(`No handler for event type: ${data.type}`);
break; break;

View File

@@ -1,10 +1,10 @@
import { ILogger } from "../..//utils/logger"; import { ILogger } from "utils/logger";
import { IConnection, IChannel, IClient, ITS5DataHandler } from "../../interfaces/teamspeak"; import { IConnection, IChannel, IClient, ITS5DataHandler } from "interfaces/teamspeak";
/** /**
* Handles data received from TS5 client (list of connections, channels and clients) * Handles data received from TS5 client (list of connections, channels and clients)
* Updates the states of App.tsx * Updates the states of useTSRemoteApp.tsx
*/ */
export class TS5DataHandler implements ITS5DataHandler { export class TS5DataHandler implements ITS5DataHandler {
// Local lists of connections, channels and clients // Local lists of connections, channels and clients
@@ -38,6 +38,18 @@ export class TS5DataHandler implements ITS5DataHandler {
this.logger = logger; this.logger = logger;
} }
get connections(): IConnection[] {
return this.localConnections;
}
get channels(): IChannel[] {
return this.localChannels;
}
get clients(): IClient[] {
return this.localClients;
}
// Update App.tsx states // Update App.tsx states
private updateConnectionsState() { private updateConnectionsState() {
this.setConnections([...this.localConnections]); this.setConnections([...this.localConnections]);

View File

@@ -1,5 +1,6 @@
import { ILogger } from "../../utils/logger"; import { ILogger } from "utils/logger";
import { IChannelInfos, IConnection, IChannel, IAuthMessage, IClientInfo, IClientMovedMessage, IClient, IClientPropertiesUpdatedMessage, ITalkStatusChangedMessage, IClientSelfPropertyUpdatedMessage, IServerPropertiesUpdatedMessage, IConnectStatusChangedMessage, IChannelsMessage, ITS5MessageHandler, ITS5DataHandler } from "../../interfaces/teamspeak"; import { IChannelInfos, IConnection, IChannel, IAuthMessage, IClientInfo, IClientMovedMessage, IClient, IClientPropertiesUpdatedMessage, ITalkStatusChangedMessage, IClientSelfPropertyUpdatedMessage, IServerPropertiesUpdatedMessage, IConnectStatusChangedMessage, IChannelsMessage, ITS5MessageHandler, ITS5DataHandler, IChannelCreatedMessage } from "interfaces/teamspeak";
import { log } from "console";
// Handle incoming messages from TS5 client // Handle incoming messages from TS5 client
export class TS5MessageHandler implements ITS5MessageHandler { export class TS5MessageHandler implements ITS5MessageHandler {
@@ -25,15 +26,13 @@ export class TS5MessageHandler implements ITS5MessageHandler {
parseChannelInfos(channelInfos: IChannelInfos, connection: IConnection) { parseChannelInfos(channelInfos: IChannelInfos, connection: IConnection) {
channelInfos.rootChannels.forEach((channel: IChannel) => { channelInfos.rootChannels.forEach((channel: IChannel) => {
this.dataHandler.addChannel({ ...channel, connection: connection }); this.dataHandler.addChannel({ ...channel, connection: connection });
if (channelInfos) {
if (channelInfos.subChannels !== null && channel.id in channelInfos.subChannels) {
channelInfos.subChannels[channel.id]?.forEach((subChannel: IChannel) => {
this.dataHandler.addChannel({ ...subChannel, connection: connection });
});
}
}
}); });
for (const key in channelInfos.subChannels) {
channelInfos.subChannels[key]?.forEach((subChannel: IChannel) => {
this.dataHandler.addChannel({ ...subChannel, connection: connection });
});
}
} }
// This message is sent by the TS5 server when the client is connected // This message is sent by the TS5 server when the client is connected
@@ -70,10 +69,9 @@ export class TS5MessageHandler implements ITS5MessageHandler {
handleClientMovedMessage(data: IClientMovedMessage) { handleClientMovedMessage(data: IClientMovedMessage) {
const client: IClient | undefined = this.dataHandler.getClientById(data.payload.clientId, data.payload.connectionId); const client: IClient | undefined = this.dataHandler.getClientById(data.payload.clientId, data.payload.connectionId);
//* This gets called when we are connecting to the server and the new clients get loaded //* This gets called when we are connecting to the server and the new clients get loaded
if (+data.payload.oldChannelId == 0) { // Create new client(when connecting to server) if (+data.payload.oldChannelId == 0) { // Create new client(when connecting to server)
//set timout to wait for channels to be created //set timeout to wait for channels to be created
setTimeout(() => { setTimeout(() => {
const newChannel = this.dataHandler.getChannelById(data.payload.newChannelId, data.payload.connectionId); const newChannel = this.dataHandler.getChannelById(data.payload.newChannelId, data.payload.connectionId);
if (newChannel !== undefined) { if (newChannel !== undefined) {
@@ -149,6 +147,7 @@ export class TS5MessageHandler implements ITS5MessageHandler {
// console.log(this.dataHandler.localClients) // console.log(this.dataHandler.localClients)
} }
handleClientSelfPropertyUpdatedMessage(data: IClientSelfPropertyUpdatedMessage) { handleClientSelfPropertyUpdatedMessage(data: IClientSelfPropertyUpdatedMessage) {
const connection: IConnection | undefined = this.dataHandler.getConnectionById(this.activeConnectionId); const connection: IConnection | undefined = this.dataHandler.getConnectionById(this.activeConnectionId);
@@ -196,4 +195,19 @@ export class TS5MessageHandler implements ITS5MessageHandler {
} }
}, 1000); }, 1000);
} }
handleChannelCreatedMessage(data: IChannelCreatedMessage) {
const connection = this.dataHandler.getConnectionById(data.payload.connectionId);
if (connection !== undefined) {
this.dataHandler.addChannel(
{
id: data.payload.channelId,
connection: connection,
order: data.payload.properties.order,
parentId: data.payload.parentId,
properties: data.payload.properties,
});
}
}
} }

View File

@@ -1,9 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { TS5ConnectionHandler } from "../handlers/teamspeak/connectionHandler"; import { TS5ConnectionHandler } from "handlers/teamspeak/connectionHandler";
import { ITSRemoteAppOptions } from "../interfaces/api"; import { ITSRemoteAppOptions } from "interfaces/api";
import { IClient, IChannel, IConnection, ITS5ConnectionHandler } from "../interfaces/teamspeak"; import { IClient, IChannel, IConnection, ITS5ConnectionHandler } from "interfaces/teamspeak";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Logger from "../utils/logger"; import Logger from "utils/logger";
export default function useTSRemoteApp(options: ITSRemoteAppOptions) { export default function useTSRemoteApp(options: ITSRemoteAppOptions) {
const [clients, setClients] = useState<IClient[]>([]); const [clients, setClients] = useState<IClient[]>([]);

View File

@@ -1,6 +1,6 @@
import useTSRemoteApp from "./hooks/useTSRemoteApp"; import useTSRemoteApp from "hooks/useTSRemoteApp";
export type { IChannel, IClient, IConnection, IAuthSenderPayload, IChannelInfos, IAuthMessage, IChannelProperties, IClientInfo, IServerProperties } from "./interfaces/teamspeak"; export type { IChannel, IClient, IConnection, IAuthSenderPayload, IChannelInfos, IAuthMessage, IChannelProperties, IClientInfo, IServerProperties } from "interfaces/teamspeak";
export default useTSRemoteApp; export default useTSRemoteApp;

View File

@@ -1,5 +1,5 @@
import { ILogger } from "../utils/logger"; import { ILogger } from "utils/logger";
import { ITSRemoteAppAuthPayloadOptions } from "./api"; import { ITSRemoteAppAuthPayloadOptions } from "interfaces/api";
// Classes // Classes
export interface ITS5ConnectionHandler { export interface ITS5ConnectionHandler {
@@ -35,6 +35,10 @@ export interface ITS5DataHandler {
getConnectionById(id: number): IConnection | undefined; getConnectionById(id: number): IConnection | undefined;
getChannelById(id: number, connectionId: number): IChannel | undefined; getChannelById(id: number, connectionId: number): IChannel | undefined;
getClientById(id: number, connectionId: number): IClient | undefined; getClientById(id: number, connectionId: number): IClient | undefined;
readonly connections: IConnection[];
readonly channels: IChannel[];
readonly clients: IClient[];
} }
export interface ITS5MessageHandler { export interface ITS5MessageHandler {
@@ -53,6 +57,7 @@ export interface ITS5MessageHandler {
handleServerPropertiesUpdatedMessage(data: IServerPropertiesUpdatedMessage): void; handleServerPropertiesUpdatedMessage(data: IServerPropertiesUpdatedMessage): void;
handleConnectStatusChangedMessage(data: IConnectStatusChangedMessage): void; handleConnectStatusChangedMessage(data: IConnectStatusChangedMessage): void;
handleChannelsMessage(data: IChannelsMessage): void; handleChannelsMessage(data: IChannelsMessage): void;
handleChannelCreatedMessage(data: IChannelCreatedMessage): void;
} }
// Remote App // Remote App
@@ -345,4 +350,14 @@ export interface IChannelsMessage {
connectionId: number; connectionId: number;
info: IChannelInfos info: IChannelInfos
} }
}
export interface IChannelCreatedMessage {
type: "channelCreated";
payload: {
channelId: number;
connectionId: number;
parentId: string;
properties: IChannelProperties;
}
} }

View File

@@ -1 +1,5 @@
{ "extends": "./tsconfig.json", "exclude": ["src/**/*.spec.ts", "src/**/*.spec.tsx"] } {
"extends": "./tsconfig.json",
"exclude": ["src/**/*.spec.ts", "src/**/*.spec.tsx"],
"compilerOptions": { "baseUrl": "./src" }
}

View File

@@ -9,7 +9,8 @@
"outDir": "dist", "outDir": "dist",
"strict": true, "strict": true,
"strictNullChecks": true, "strictNullChecks": true,
"target": "es5" "target": "es5",
"baseUrl": "./src"
}, },
"include": ["src"] "include": ["src"]
} }

View File

@@ -3,6 +3,7 @@
"composite": true, "composite": true,
"skipLibCheck": true, "skipLibCheck": true,
"module": "ESNext", "module": "ESNext",
"baseUrl": "./src",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },