Compare commits
26 Commits
96a5e5896b
...
1.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
| d3a295d598 | |||
| d97abc00e2 | |||
| cada5487bf | |||
| 2f9c65512b | |||
| edd87375c3 | |||
| b2d5b84435 | |||
| c460dc5385 | |||
| f54e76ab72 | |||
| fb1555338d | |||
| c98c917594 | |||
| cfb72d1772 | |||
| 01d07d65d1 | |||
| c6ad01b710 | |||
| 113cff5512 | |||
| 2c91b73a60 | |||
| 866c8a1838 | |||
| 926a28b7f9 | |||
| 9d6a18dda4 | |||
| 76f573023f | |||
| b217123b99 | |||
| 59f7e10dd7 | |||
| 56a0167120 | |||
| dc782003b0 | |||
| 2af9142d6c | |||
| 40905b225c | |||
| 77e032fdb2 |
@@ -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
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@chatenium/chatenium-sdk",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.1",
|
||||
"description": "A library for interacting with the Chatenium API",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -39,7 +39,6 @@
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^10.4.0",
|
||||
"axios": "^1.14.0",
|
||||
"dotenv": "^17.4.0",
|
||||
"msw": "^2.12.14",
|
||||
"uuid": "^13.0.0"
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
32
src/core/permissions.ts
Normal file
32
src/core/permissions.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export const permissions = {
|
||||
createAndEditCategories: 2,
|
||||
deleteCategories: 4,
|
||||
createAndEditChannels: 8,
|
||||
deleteChannels: 16,
|
||||
deleteAnyMessage: 32,
|
||||
pinMessages: 64,
|
||||
createAndEditRanks: 128,
|
||||
deleteRanks: 256,
|
||||
changeNetworkNamePictureAndVisibility: 512,
|
||||
createEmojis: 1024,
|
||||
deleteEmojis: 2048,
|
||||
manageEmbed: 4096,
|
||||
createWebhooks: 8192,
|
||||
deleteWebhooks: 16384,
|
||||
createInvites: 32768,
|
||||
deleteInvites: 65536,
|
||||
sendMessages: 131072,
|
||||
seeChannels: 262144,
|
||||
banMembers: 524288,
|
||||
kickMembers: 1048576,
|
||||
unAndAssignRanksToMember: 2097152,
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the set of permissions includes the permission. Note that network owners have regular permissions just like the other members, so if the userid matches with the network's creator ID, then the permission is automatically granted. This logic is not included in this function.
|
||||
* @param permissions
|
||||
* @param permission
|
||||
*/
|
||||
export function permissionGranted(permissions: number, permission: number): Boolean {
|
||||
return (permissions & permission) === permission;
|
||||
}
|
||||
@@ -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,10 +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 => {
|
||||
@@ -80,6 +87,9 @@ export class WebSocketHandler {
|
||||
|
||||
public registerService(service: WSListenerPipe) {
|
||||
console.log("Registering service", service)
|
||||
if (this.connId) {
|
||||
service.onNewConnId(this.connId)
|
||||
}
|
||||
this.listeners.add(service);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface JoinWebsocketRoomReq {
|
||||
channelId: string
|
||||
networkId: string
|
||||
categoryId: string
|
||||
disableAutoRemove: boolean
|
||||
}
|
||||
|
||||
export interface StreamRegistry {
|
||||
|
||||
@@ -24,4 +24,5 @@ export interface Attachment {
|
||||
path: string
|
||||
height: number
|
||||
width: number
|
||||
extraMetaData: Record<string, any> // Used by clients
|
||||
}
|
||||
@@ -58,6 +58,7 @@ export interface JoinWsRoomReq {
|
||||
connId: string
|
||||
chatid: string
|
||||
userid: string
|
||||
disableAutoRemove: boolean
|
||||
}
|
||||
|
||||
// Response schemas
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -78,6 +78,7 @@ export interface JoinWsRoomReq {
|
||||
networkId: string
|
||||
categoryId: string
|
||||
userid: string
|
||||
disableAutoRemove: boolean
|
||||
}
|
||||
|
||||
// Response schemas
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
})
|
||||
@@ -36,7 +36,7 @@ export class DMService {
|
||||
})
|
||||
WebSocketHandler.getInstance().registerService({
|
||||
identifier: chatid,
|
||||
onNewConnId: this.onNewConnId,
|
||||
onNewConnId: newConnId => this.onNewConnId(newConnId),
|
||||
onNewMessage: wsMessageListener,
|
||||
})
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -88,21 +100,23 @@ export class SessionManager {
|
||||
* Validates and updates all sessions and returns with a new session list
|
||||
* @param sessions
|
||||
*/
|
||||
updateSessions(sessions: Session[]): Session[] {
|
||||
sessions.forEach(async session => {
|
||||
const index = sessions.indexOf(session)
|
||||
async updateSessions(sessions: Session[]): Promise<Session[]> {
|
||||
const activeSessions: Session[] = [];
|
||||
|
||||
for (const session of sessions) {
|
||||
if (!await this.validateSession(session.token)) {
|
||||
this.database.delete("sessions", session.userData.userid)
|
||||
this.keyring.delete(session.userData.userid)
|
||||
sessions.splice(index, 1)
|
||||
this.database.delete("sessions", session.userData.userid);
|
||||
this.keyring.delete(session.userData.userid);
|
||||
console.warn(`Validating session for user ${session.userData.userid} failed. Deleting session...`)
|
||||
continue;
|
||||
}
|
||||
|
||||
const updatedUserData = await this.updateUserData(session)
|
||||
this.database.set("sessions", session.userData.userid, updatedUserData)
|
||||
sessions[index] = updatedUserData
|
||||
})
|
||||
const updatedUserData = await this.updateUserData(session);
|
||||
this.database.set("sessions", session.userData.userid, updatedUserData);
|
||||
activeSessions.push(updatedUserData);
|
||||
}
|
||||
|
||||
return sessions
|
||||
return activeSessions;
|
||||
}
|
||||
|
||||
private async validateSession(token: string): Promise<boolean> {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
[
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user