24 Commits

Author SHA1 Message Date
edd87375c3 Update package.json
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
Publish to NPM / build-and-publish (release) Successful in 17s
2026-04-16 18:15:30 +02:00
b2d5b84435 Update package.json
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-16 18:13:06 +02:00
c460dc5385 Updated networkService.ts to use new /v2/ endpoint for uploading new network pictures
All checks were successful
Setup testing environment and test the code / build (push) Successful in 26s
Publish to NPM / build-and-publish (release) Successful in 37s
2026-04-16 16:55:48 +02:00
f54e76ab72 Updated extraMetadata to allow any type of data
All checks were successful
Setup testing environment and test the code / build (push) Successful in 26s
Publish to NPM / build-and-publish (release) Successful in 18s
2026-04-15 16:11:01 +02:00
fb1555338d Update WS
Some checks failed
Publish to NPM / build-and-publish (release) Successful in 27s
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-14 16:44:27 +02:00
c98c917594 Update WS 2026-04-14 16:44:19 +02:00
cfb72d1772 Fixed types in getQuick functions
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m48s
Publish to NPM / build-and-publish (release) Successful in 27s
2026-04-14 16:05:09 +02:00
01d07d65d1 WebSocket Hotfix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m9s
Publish to NPM / build-and-publish (release) Successful in 32s
2026-04-11 17:25:10 +02:00
c6ad01b710 Fix textChannelService.ts websocket
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m1s
Publish to NPM / build-and-publish (release) Successful in 30s
2026-04-11 17:14:39 +02:00
113cff5512 Fix textChannelService.ts websocket
Some checks failed
Setup testing environment and test the code / build (push) Failing after 1s
Publish to NPM / build-and-publish (release) Successful in 40s
2026-04-11 17:03:19 +02:00
2c91b73a60 Fix textChannelService.ts websocket
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-11 17:02:53 +02:00
866c8a1838 Removed dotenv
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m22s
Publish to NPM / build-and-publish (release) Successful in 29s
2026-04-10 21:17:16 +02:00
926a28b7f9 SessionManager hotfix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 2m37s
Publish to NPM / build-and-publish (release) Successful in 1m44s
2026-04-10 19:18:13 +02:00
9d6a18dda4 Updated Attachment type
All checks were successful
Setup testing environment and test the code / build (push) Successful in 2m42s
Publish to NPM / build-and-publish (release) Successful in 2m47s
2026-04-10 09:18:16 +02:00
76f573023f WebSocket onNewConnId scope hotfix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m10s
Publish to NPM / build-and-publish (release) Successful in 32s
2026-04-10 08:14:47 +02:00
b217123b99 Fix tests
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m9s
Publish to NPM / build-and-publish (release) Successful in 32s
2026-04-10 08:01:11 +02:00
59f7e10dd7 Race-condition fix
Some checks failed
Setup testing environment and test the code / build (push) Failing after 1m9s
2026-04-10 07:59:45 +02:00
56a0167120 WebSocket update
Some checks failed
Setup testing environment and test the code / build (push) Failing after 1m26s
Publish to NPM / build-and-publish (release) Successful in 29s
2026-04-10 07:38:52 +02:00
dc782003b0 Improve DX for message sending
Some checks failed
Setup testing environment and test the code / build (push) Failing after 0s
Publish to NPM / build-and-publish (release) Successful in 35s
2026-04-09 16:56:11 +02:00
2af9142d6c Improve DX for message sending
Some checks failed
Setup testing environment and test the code / build (push) Failing after 1m39s
Publish to NPM / build-and-publish (release) Failing after 33s
2026-04-09 16:53:08 +02:00
40905b225c Improve DX for message sending
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-09 16:52:55 +02:00
77e032fdb2 HotFix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 59s
Publish to NPM / build-and-publish (release) Successful in 31s
2026-04-09 12:04:42 +02:00
96a5e5896b HotFix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m13s
Publish to NPM / build-and-publish (release) Successful in 33s
2026-04-09 11:56:40 +02:00
14fe7ef41d HotFix 2026-04-09 11:56:29 +02:00
23 changed files with 90 additions and 55 deletions

View File

@@ -45,13 +45,11 @@ jobs:
with:
node-version: '24'
- name: Create .env file
run: |
echo "API_URL=http://api:3000" >> .env
echo "CDN_URL=http://cdn:4000" >> .env
echo "WS_URL=ws://api:3000" >> .env
- name: Run Vitest
env:
API_URL: http://api:3000
CDN_URL: http://cdn:4000
WS_URL: ws://api:3000
run: |
npm install
npm test --experimental-websocket

8
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "chatenium-sdk",
"version": "1.0.0",
"name": "@chatenium/chatenium-sdk",
"version": "1.0.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "chatenium-sdk",
"version": "1.0.0",
"name": "@chatenium/chatenium-sdk",
"version": "1.0.8",
"dependencies": {
"@faker-js/faker": "^10.4.0",
"axios": "^1.14.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@chatenium/chatenium-sdk",
"version": "1.0.8",
"version": "1.1.13",
"description": "A library for interacting with the Chatenium API",
"type": "module",
"main": "dist/index.js",
@@ -23,6 +23,7 @@
"README.md",
"LICENSE"
],
"license": "GPL-3.0-or-later",
"scripts": {
"build": "tsc",
"test": "vitest run",
@@ -39,7 +40,6 @@
"dependencies": {
"@faker-js/faker": "^10.4.0",
"axios": "^1.14.0",
"dotenv": "^17.4.0",
"msw": "^2.12.14",
"uuid": "^13.0.0"
}

View File

@@ -5,19 +5,12 @@ export interface SDKConfig {
}
declare const process: any;
declare const require: any;
const isNode =
typeof process !== 'undefined' &&
typeof process.versions !== 'undefined' &&
typeof process.versions.node !== 'undefined';
if (isNode) {
try {
require('dotenv').config();
} catch { }
}
const getEnv = (key: string): string | undefined => {
if (!isNode) return undefined;
return process.env?.[key];

View File

@@ -43,6 +43,13 @@ export class WebSocketHandler {
this.connection = new WebSocket(`${environment.get().wsUrl}/v2/ws?userid=${userid}&access_token=${resp.data.token}`)
console.log("Connected to websocket successfully")
this.startListening()
this.connection.onclose = () => {
console.error("The WebSocket connection was closed unexpectedly. Reconnecting...")
setTimeout(() => {
this.connect(userid, token)
}, 3000)
}
return
} catch (e) {
console.log(e)
@@ -64,9 +71,10 @@ export class WebSocketHandler {
if (payl.action == "connectionId") {
console.log("ConnectionID received")
const data: WSConnIdPayload = JSON.parse(payl.data);
this.connectionId = data.connId;
this.listeners.forEach(listener => {
console.log(data.connId, listener)
listener.onNewConnId(data.connId)
this.connectionId = data.connId;
})
} else {
this.listeners.forEach(listener => {
@@ -78,6 +86,10 @@ export class WebSocketHandler {
}
public registerService(service: WSListenerPipe) {
console.log("Registering service", service)
if (this.connId) {
service.onNewConnId(this.connId)
}
this.listeners.add(service);
}

View File

@@ -17,6 +17,7 @@ export interface JoinWebsocketRoomReq {
channelId: string
networkId: string
categoryId: string
disableAutoRemove: boolean
}
export interface StreamRegistry {

View File

@@ -24,4 +24,5 @@ export interface Attachment {
path: string
height: number
width: number
extraMetaData: Record<string, any> // Used by clients
}

View File

@@ -58,6 +58,7 @@ export interface JoinWsRoomReq {
connId: string
chatid: string
userid: string
disableAutoRemove: boolean
}
// Response schemas

View File

@@ -41,5 +41,5 @@ export interface FileData {
}
export interface FileUploadProgressListener {
fileProgressUpdate: (fileId: string, allChunks: number, chunksDone: number) => void
fileProgressUpdate: (tempMsgId: string, fileId: string, allChunks: number, chunksDone: number) => void
}

View File

@@ -208,9 +208,11 @@ export interface GetMembersReq {
}
export interface UploadNewPictureReq {
picId: string
userid: string
networkId: string
data: string
isImage: boolean
monogramColors: RGB | null
}
export interface ChangeVisibilityReq {
@@ -255,6 +257,7 @@ export interface JoinWebSocketRoomReq {
userid: string
connId: string
networkId: string
disableAutoRemove: boolean
}
export interface GetFromInviteReq {

View File

@@ -78,6 +78,7 @@ export interface JoinWsRoomReq {
networkId: string
categoryId: string
userid: string
disableAutoRemove: boolean
}
// Response schemas

View File

@@ -31,7 +31,7 @@ export class BroadcastChannelService {
})
WebSocketHandler.getInstance().registerService({
identifier: channelId,
onNewConnId: this.onNewConnId,
onNewConnId: newConnId => this.onNewConnId(newConnId),
onNewMessage: wsMessageListener,
})
}
@@ -81,6 +81,7 @@ export class BroadcastChannelService {
networkId: this.networkId,
connId: WebSocketHandler.getInstance().connId,
categoryId: this.categoryId,
disableAutoRemove: true
});
return
} catch (e) {

View File

@@ -32,7 +32,7 @@ export class ChatService {
})
WebSocketHandler.getInstance().registerService({
identifier: userid,
onNewConnId: this.onNewConnId,
onNewConnId: newConnId => this.onNewConnId(newConnId),
onNewMessage: wsMessageListener,
})
}
@@ -58,7 +58,7 @@ export class ChatService {
}
}
async getQuick(): Promise<Message[]> {
async getQuick(): Promise<Chat[]> {
const chats = await this.database.get("chats", this.userid)
if (chats) {
return JSON.parse(chats)

View File

@@ -23,7 +23,7 @@ describe("DmService", () => {
it('should send a new message', async () => {
const message = faker.internet.displayName()
const newMessage = await service.sendMessage(message)
const newMessage = await service.sendMessage("", message)
expect(newMessage.message).toBe(message)
});
})

View File

@@ -36,13 +36,13 @@ export class DMService {
})
WebSocketHandler.getInstance().registerService({
identifier: chatid,
onNewConnId: this.onNewConnId,
onNewConnId: newConnId => this.onNewConnId(newConnId),
onNewMessage: wsMessageListener,
})
}
private onNewConnId(newConnId: string) {
console.log("NetworkService: New connection id")
console.log("DmService: New connection id")
this.client.defaults.headers["X-WS-ID"] = newConnId;
this.joinWebSocketRoom().then()
}
@@ -130,17 +130,18 @@ export class DMService {
/**
* Sends a new message to the chat
* @param tempMsgId
* @param message
* @param replyTo
* @param replyToMessage
* @param attachments
* @param progressListener
*/
async sendMessage(message: string, replyTo: string | null = null, replyToMessage: string | null = null, attachments: FileData[] | null = null, progressListener: FileUploadProgressListener | null = null): Promise<Message> {
async sendMessage(tempMsgId: string, message: string, replyTo: string | null = null, replyToMessage: string | null = null, attachments: FileData[] | null = null, progressListener: FileUploadProgressListener | null = null): Promise<Message> {
let uploadId = ""
if (attachments) {
const uploader = new FileUploadService(this.token)
uploadId = await uploader.uploadFiles(this.chatid, this.userid, attachments, progressListener!)
uploadId = await uploader.uploadFiles(tempMsgId, this.chatid, this.userid, attachments, progressListener!)
}
try {
const resp = await this.client.post<Message>("chat/dm/finishMessage", <FinishMessageReq>{
@@ -245,10 +246,11 @@ export class DMService {
*/
async joinWebSocketRoom(): Promise<void> {
try {
await this.client.post("v2/chat/dm/joinWebSocketRoom", <JoinWsRoomReq>{
await this.client.post("v2/chat/dm/joinWebSocketRoom", <JoinWsRoomReq>{
chatid: this.chatid,
userid: this.userid,
connId: WebSocketHandler.getInstance().connId,
disableAutoRemove: true
});
return
} catch (e) {

View File

@@ -4,7 +4,7 @@ import {FileUploadService} from './fileUploadService.js';
describe("fileUploadService", () => {
it('should upload files', async () => {
const service = new FileUploadService("");
const uploadId = await service.uploadFiles("", "", [], {
const uploadId = await service.uploadFiles("","", "", [], {
fileProgressUpdate: () => {}
})
expect(uploadId).toBe("MockUploadId")

View File

@@ -39,12 +39,13 @@ export class FileUploadService {
/**
* Automatically registers the upload, calculates chunksize and uploads each chunk and returns with an uploadId that must be provided when sending the message
* @param tempMsgId
* @param roomId chatid or channelId
* @param userid
* @param files
* @param listener
*/
async uploadFiles(roomId: string, userid: string, files: FileData[], listener: FileUploadProgressListener): Promise<string> {
async uploadFiles(tempMsgId: string, roomId: string, userid: string, files: FileData[], listener: FileUploadProgressListener): Promise<string> {
let registrations: FileUploadRegistration[] = [];
files.forEach(file => {
@@ -63,7 +64,7 @@ export class FileUploadService {
files: registrations,
});
for (let filesUploaded = 0; filesUploaded < files.length; filesUploaded++) {
await this.uploadFile(resp.data.uploadId, roomId, userid, files[filesUploaded], registrations[filesUploaded], listener)
await this.uploadFile(tempMsgId, resp.data.uploadId, roomId, userid, files[filesUploaded], registrations[filesUploaded], listener)
}
await this.finishUpload(roomId, userid, resp.data.uploadId)
return resp.data.uploadId
@@ -93,7 +94,7 @@ export class FileUploadService {
}
}
private async uploadFile(uploadId: string, roomId: string, userid: string, file: FileData, registration: FileUploadRegistration, listener: FileUploadProgressListener): Promise<void> {
private async uploadFile(tempMsgId: string, uploadId: string, roomId: string, userid: string, file: FileData, registration: FileUploadRegistration, listener: FileUploadProgressListener): Promise<void> {
const chunkSize = this.calculateChunkSize(file.data.size);
const totalChunks = Math.ceil(file.data.size / chunkSize);
@@ -105,7 +106,7 @@ export class FileUploadService {
const chunk = new Uint8Array(arrayBuffer.slice(start, end));
const base64 = this.uint8ToBase64(chunk);
await this.uploadChunk(uploadId, roomId, userid, registration.fileId, base64);
listener.fileProgressUpdate(file.fileId, totalChunks, i)
listener.fileProgressUpdate(tempMsgId, file.fileId, totalChunks, i)
}
}

View File

@@ -39,7 +39,7 @@ export class NetworkService {
})
WebSocketHandler.getInstance().registerService({
identifier: networkId,
onNewConnId: this.onNewConnId,
onNewConnId: newConnId => this.onNewConnId(newConnId),
onNewMessage: wsMessageListener,
})
}
@@ -108,7 +108,7 @@ export class NetworkService {
}
}
async getQuick(): Promise<Message[]> {
async getQuick(): Promise<Network[]> {
const networks = await this.database.get("networks", this.userid)
if (networks) {
return JSON.parse(networks)
@@ -711,14 +711,18 @@ export class NetworkService {
/**
* Uploads a new network picture
* @param picId
* @param isImage
* @param image
* @param colors
*/
async uploadNewPic(picId: string): Promise<void> {
async uploadNewPic(isImage: boolean, image: string | null, colors: RGB | null): Promise<void> {
try {
await this.client.patch<PublicUserData[]>("network/uploadNewPic", <UploadNewPictureReq>{
await this.client.patch<PublicUserData[]>("v2/network/uploadNewPic", <UploadNewPictureReq>{
userid: this.userid,
networkId: this.networkId,
picId: picId,
data: image,
isImage: isImage,
monogramColors: colors
});
return
} catch (e) {
@@ -863,6 +867,7 @@ export class NetworkService {
userid: this.userid,
networkId: this.networkId,
connId: connId,
disableAutoRemove: true
});
return
} catch (e) {

View File

@@ -36,14 +36,21 @@ export class SessionManager {
async loadSessions(): Promise<Session[]> {
const tokens = await this.keyring.getAll()
const sessions: Session[] = []
for (const tokenKey of tokens) {
const token = await this.keyring.get(tokenKey)
const userData = await this.database.get("sessions", tokenKey)
if (userData) {
sessions.push({
token: token,
userData: JSON.parse(userData)
})
try {
const token = await this.keyring.get(tokenKey)
const userData = await this.database.get("sessions", tokenKey)
if (userData && userData.trim().length > 0) {
sessions.push({
token: token,
userData: JSON.parse(userData)
})
}
} catch (e) {
console.error(`Failed to parse session for ${tokenKey}:`, e)
continue
}
}
@@ -70,6 +77,11 @@ export class SessionManager {
*/
async loadPreferredSession() {
const sessions = await this.loadSessions()
if (sessions.length == 0) {
throw new Error("No sessions found")
}
let preferredUser = await this.getPreferredUser()
if (preferredUser == "") {
preferredUser = sessions[0].userData.userid

View File

@@ -40,7 +40,7 @@ export class TextChannelServiceService {
})
WebSocketHandler.getInstance().registerService({
identifier: channelId,
onNewConnId: this.onNewConnId,
onNewConnId: newConnId => this.onNewConnId(newConnId),
onNewMessage: wsMessageListener,
})
}
@@ -48,6 +48,7 @@ export class TextChannelServiceService {
private onNewConnId(newConnId: string) {
console.log("NetworkService: New connection id")
this.client.defaults.headers["X-WS-ID"] = newConnId;
this.joinWebSocketRoom().then()
}
/**
@@ -137,17 +138,18 @@ export class TextChannelServiceService {
/**
* Sends a new message to the chat
* @param tempMsgId
* @param message
* @param replyTo
* @param replyToMessage
* @param attachments
* @param progressListener
*/
async sendMessage(message: string, replyTo: string | null = null, replyToMessage: string | null = null, attachments: FileData[] | null = null, progressListener: FileUploadProgressListener | null = null): Promise<Message> {
async sendMessage(tempMsgId: string, message: string, replyTo: string | null = null, replyToMessage: string | null = null, attachments: FileData[] | null = null, progressListener: FileUploadProgressListener | null = null): Promise<Message> {
let uploadId = ""
if (attachments) {
const uploader = new FileUploadService(this.token)
uploadId = await uploader.uploadFiles(this.channelId, this.userid, attachments, progressListener!)
uploadId = await uploader.uploadFiles(tempMsgId, this.channelId, this.userid, attachments, progressListener!)
}
try {
const resp = await this.client.post<Message>("network/channel/finishMessage", <FinishMessageReq>{
@@ -262,12 +264,13 @@ export class TextChannelServiceService {
*/
async joinWebSocketRoom(): Promise<void> {
try {
const resp = await this.client.patch("network/channel/joinWebSocketRoom", <JoinWsRoomReq>{
const resp = await this.client.post("v2/network/channel/joinWebSocketRoom", <JoinWsRoomReq>{
networkId: this.networkId,
channelId: this.channelId,
categoryId: this.categoryId,
userid: this.userid,
connId: WebSocketHandler.getInstance().connId,
disableAutoRemove: true
});
return
} catch (e) {

View File

@@ -38,7 +38,7 @@ describe("DmService Integration Testing", () => {
it('should send a message', async () => {
const message = faker.lorem.paragraph()
const newMessage = await service.sendMessage(message)
const newMessage = await service.sendMessage("", message)
expect(newMessage.message).toBe(message)
});

View File

@@ -18,6 +18,7 @@ describe("FileUploadService Integration Testing", () => {
const service = new FileUploadService(FILE_UPL_SERVICE_TESTING_TOKEN);
await service.uploadFiles(
"",
FILE_UPL_SERVICE_TESTING_CHAT_ID,
FILE_UPL_SERVICE_TESTING_USER_ID,
[

View File

@@ -43,7 +43,7 @@ describe("DmService Integration Testing", () => {
it('should send a message', async () => {
const message = faker.lorem.paragraph()
const newMessage = await service.sendMessage(message)
const newMessage = await service.sendMessage("", message)
expect(newMessage.message).toBe(message)
});