21 Commits

Author SHA1 Message Date
d04bd6a437 HotFix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 57s
Publish to NPM / build-and-publish (release) Successful in 33s
2026-04-09 11:18:47 +02:00
0e553767b6 HotFix
Some checks failed
Publish to NPM / build-and-publish (release) Has been cancelled
Setup testing environment and test the code / build (push) Failing after 1m1s
2026-04-09 11:16:45 +02:00
b7af5497a4 Fix DM WebSocket
Some checks failed
Setup testing environment and test the code / build (push) Failing after 1m22s
Publish to NPM / build-and-publish (release) Successful in 30s
2026-04-09 11:12:41 +02:00
1cd629e3c1 loadSessions hotfix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m20s
Publish to NPM / build-and-publish (release) Successful in 27s
2026-04-08 19:18:06 +02:00
7d50692ece Make storage async
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m4s
Publish to NPM / build-and-publish (release) Successful in 41s
2026-04-08 18:10:46 +02:00
d7422efcf0 Quick-fix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m2s
2026-04-08 16:56:09 +02:00
1ccf534e04 Quick-fix
Some checks failed
Setup testing environment and test the code / build (push) Successful in 1m1s
Publish to NPM / build-and-publish (release) Failing after 29s
2026-04-08 16:48:11 +02:00
7ba341203d Quick-fix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m6s
Publish to NPM / build-and-publish (release) Successful in 36s
2026-04-08 16:44:41 +02:00
54d466fb27 Quick-fix
All checks were successful
Publish to NPM / build-and-publish (release) Successful in 34s
Setup testing environment and test the code / build (push) Successful in 1m30s
2026-04-08 16:37:32 +02:00
07d30f7e83 Quick-fix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m28s
2026-04-08 14:22:58 +02:00
cd806b7d17 Update package.json
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-08 13:59:00 +02:00
7daf542961 Update .gitea/workflows/publish.yml
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-08 13:55:29 +02:00
f911224847 Update .gitea/workflows/publish.yml
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-08 13:53:45 +02:00
c8d2de9f9d Merge remote-tracking branch 'origin/main'
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-08 13:51:47 +02:00
f8de78f3ab Type fixes 2026-04-08 13:51:43 +02:00
e07114b1c0 Update .gitea/workflows/publish.yml
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-08 13:50:17 +02:00
e8c6b9c920 Update .gitea/workflows/publish.yml
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-08 13:47:51 +02:00
0a97a2cc48 Add .gitea/workflows/publish
Some checks failed
Setup testing environment and test the code / build (push) Has been cancelled
2026-04-08 13:47:38 +02:00
0b38b002df Implemented SessionManager and TextChannelService + several improvements and fixes
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m13s
2026-04-08 13:21:11 +02:00
a9322e3454 Implemented BroadcastChannelService and FileTransferService
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m17s
2026-04-08 08:46:16 +02:00
e6798b4be8 Mock database hotfix
All checks were successful
Setup testing environment and test the code / build (push) Successful in 1m6s
2026-04-07 11:37:29 +02:00
66 changed files with 1325 additions and 177 deletions

View File

@@ -0,0 +1,30 @@
name: Publish to NPM
on:
workflow_dispatch:
release:
types: [published]
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Install dependencies
run: npm ci
- name: Build TypeScript
run: npm run build
- name: Add NPM token
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
- name: Publish to NPM
run: npm publish --access public

3
.gitignore vendored
View File

@@ -13,4 +13,5 @@ yarn-error.log*
.DS_Store
.env
.env.*
!.env.example
!.env.example
/dist

6
README.md Normal file
View File

@@ -0,0 +1,6 @@
# Chatenium SDK For TypeScript
A library for interacting with the Chatenium API.
## Quick Start
```aiignore
npm install @chatenium/chatenium-sdk
```

View File

@@ -1,8 +1,28 @@
{
"name": "chatenium-sdk",
"version": "1.0.0",
"description": "",
"name": "@chatenium/chatenium-sdk",
"version": "1.0.8",
"description": "A library for interacting with the Chatenium API",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./core/*": "./dist/core/*.js",
"./services/*": "./dist/services/*.js",
"./domain/*": "./dist/domain/*.js",
"./mocks/*": "./dist/mocks/*.js",
"./storage/*": "./dist/storage/*.js"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"scripts": {
"build": "tsc",
"test": "vitest run",
@@ -15,7 +35,7 @@
"typescript": "^5.5.3",
"vitest": "^4.1.2"
},
"private": true,
"private": false,
"dependencies": {
"@faker-js/faker": "^10.4.0",
"axios": "^1.14.0",

View File

@@ -4,6 +4,9 @@ export interface SDKConfig {
wsUrl: string;
}
declare const process: any;
declare const require: any;
const isNode =
typeof process !== 'undefined' &&
typeof process.versions !== 'undefined' &&

View File

@@ -1,5 +1,5 @@
import axios, {AxiosInstance} from 'axios';
import {environment} from "./environment";
import {environment} from './environment.js';
export const getClient = (cdn: boolean) => {
const env = environment.get();

View File

@@ -4,12 +4,12 @@ import {
WSMakeTokenReq,
WSMakeTokenResp,
WSMessagePayload
} from "../domain/websocket.schema";
import {getClient} from "./http";
import {CreateNetworkReq, Network} from "../domain/networkService.schema";
} from '../domain/websocket.schema.js';
import {getClient} from './http.js';
import {CreateNetworkReq, Network} from '../domain/networkService.schema.js';
import {isAxiosError} from "axios";
import {GenericErrorBody} from "../domain/http.schema";
import {environment} from "./environment";
import {GenericErrorBody} from '../domain/http.schema.js';
import {environment} from './environment.js';
export class WebSocketHandler {
private static instance: WebSocketHandler;

View File

@@ -0,0 +1,28 @@
export interface CreateServerReq {
type: "rtmp",
channelId: string
categoryId: string
networkId: string
}
export interface GetRTMPDataReq {
channelId: string
networkId: string
categoryId: string
}
export interface JoinWebsocketRoomReq {
userid: string
connId: string
channelId: string
networkId: string
categoryId: string
}
export interface StreamRegistry {
streamKey: string
status: "idling" | "broadcasting" | "broadcasting_starting"
type: "rtmp"
streamURL: string
channelId: string
}

View File

@@ -1,4 +1,4 @@
import {Attachment, TimeStamp} from "./common.schema";
import {Attachment, TimeStamp} from './common.schema.js';
export interface GetMessageReq {
from: number

View File

@@ -0,0 +1,95 @@
export interface StartNewFileTransferReq {
userid: string
targetUserId: string
metadata: TransferableFileMetadata[]
}
export interface AcceptFileTransferReq {
userid: string
senderId: string
transferId: string
}
export interface DeclineFileTransferReq {
userid: string
senderId: string
transferId: string
}
export interface FileTransferSendOfferRTCReq {
userid: string
peerId: string
transferId: string
offer: string
}
export interface FileTransferSendAnswerRTCReq {
userid: string
peerId: string
transferId: string
answer: string
}
export interface FileTransferSendICERTCReq {
userid: string
peerId: string
transferId: string
candidate: string
}
// Response schemas
export interface StartNewFileTransferResp {
transferId: string
}
// WebSocket payloads
export interface WSNewFileTransferPayload {
from: string
transferId: string
metadata: TransferableFileMetadata[]
}
export interface WSFileTransferAcceptedPayload {
transferId: string
rtcConfig: RTCConfiguration
}
export interface WSFileTransferDeclinedPayload {
transferId: string
}
export interface WSFileTransferRTCOfferPayload {
transferId: string
offer: string
}
export interface WSFileTransferRTCAnswerPayload {
transferId: string
answer: string
}
export interface WSFileTransferRTCIcePayload {
transferId: string
candidate: string
}
// DataChannel payloads
export interface DCStartNewFilePayload {
fileId: string
fileIndex: number
fileName: string
totalChunks: number
}
export interface DCTransferFilePayload {
fileId: string
fileIndex: string
chunk: string
}
// Types
export interface TransferableFileMetadata {
fileId: string
name: string
size: number
}

View File

@@ -33,8 +33,13 @@ export interface FileUploadRegistration {
}
export interface FileData {
fileId: string
name: string
extension: string
type: string
data: File
}
export interface FileUploadProgressListener {
fileProgressUpdate: (fileId: string, allChunks: number, chunksDone: number) => void
}

View File

@@ -1,5 +1,5 @@
// Request schemas
import {PublicUserData, RGB, TimeStamp} from "./common.schema";
import {PublicUserData, RGB, TimeStamp} from './common.schema.js';
export interface GetInvitesReq {
networkId: string

View File

@@ -1,4 +1,4 @@
import {PublicUserData, TimeStamp} from "./common.schema";
import {PublicUserData, TimeStamp} from './common.schema.js';
// Request schemas
export interface GetReq {

View File

@@ -0,0 +1,19 @@
import {PublicUserData} from './common.schema.js';
import {GIF, PersonalUserData} from './userService.schema.js';
export interface Session {
userData: PersonalUserData
token: string
}
export interface ValidateSessionReq {
token: string
}
export interface ValidateSessionResp {
validationOk: boolean
}
export interface UpdateUserDataReq {
userid: string
}

View File

@@ -0,0 +1,138 @@
import {Attachment, PublicUserData, TimeStamp} from './common.schema.js';
export interface GetMessageReq {
from: number
channelId: string
networkId: string
categoryId: string
}
export interface GetMessagePosReq {
messageId: string
channelId: string
networkId: string
categoryId: string
}
export interface GetPinnedMessagesReq {
channelId: string
networkId: string
categoryId: string
}
export interface EditMessageReq {
message: string
messageId: string
channelId: string
networkId: string
categoryId: string
userid: string
}
export interface FinishMessageReq {
uploadId: string | null
message: string
replyTo: string
replyToMessage: string
channelId: string
networkId: string
categoryId: string
userid: string
}
export interface ReadMessagesReq {
channelId: string
networkId: string
categoryId: string
userid: string
}
export interface PinMessageReq {
channelId: string
networkId: string
categoryId: string
messageId: string
userid: string
message: string
}
export interface UnpinMessageReq {
channelId: string
networkId: string
categoryId: string,
messageId: string,
userid: string
}
export interface DeleteMessagesReq {
messageIds: string[]
channelId: string
networkId: string
categoryId: string
userid: string
}
export interface JoinWsRoomReq {
connId: string
channelId: string
networkId: string
categoryId: string
userid: string
}
// Response schemas
export interface GetMessagePosResp {
messagePos: number
}
// Types
export interface Message {
msgid: string
author: PublicUserData
message: string
sent_at: TimeStamp
isEdited: boolean
channelId: string
networkId: string
categoryId: string
files: Attachment[]
seen: boolean
replyTo: string
replyToId: string
forwardedFrom: string
forwardedFromName: string
}
export interface PinnedMessage {
message: string
messageId: string
}
// WebSocket payloads
export interface WSMessageDeletedPayload {
messageId: string
}
export interface WSMessageEditedPayload {
messageId: string
message: string
}
export interface WSMessagePinnedPayload {
channelId: string
networkId: string
categoryId: string
messageId: string
message: string
}
export interface WSMessagesReadPayload {
userid: string
}
export interface WSMessageUnpinnedPayload {
channelId: string
networkId: string
categoryId: string
messageId: string
}

View File

@@ -1,4 +1,4 @@
import {TimeStamp} from "./common.schema";
import {TimeStamp} from './common.schema.js';
export interface ChangeUsernameReq {
newUsername: string;

View File

@@ -5,8 +5,8 @@ import {
OtpSendCodeReq,
OtpVerifyCodeReq, PleVerifyCodeReq, PleVerifyCodeResp, RegisterReq, SignInSuccessResp,
UserDataValidationResp, VerifyPasswordResetReq
} from "../../domain/authService.schema";
import {GenericSuccessBody} from "../../domain/http.schema";
} from '../../domain/authService.schema.js';
import {GenericSuccessBody} from '../../domain/http.schema.js';
export const networkHandlers = [
http.get('*/user/authOptions', () => {

View File

@@ -0,0 +1,11 @@
import {http, HttpResponse} from "msw";
import {GetRTCAccessResp} from '../../domain/callService.schema.js';
import {StreamRegistry} from '../../domain/broadcastChannelService.schema.js';
export const brcChanHandlers = [
http.get('*/network/channel/rtmpData', () => {
return HttpResponse.json(<StreamRegistry>{
status: "broadcasting_starting"
})
}),
]

View File

@@ -1,5 +1,5 @@
import {http, HttpResponse} from "msw";
import {GetRTCAccessResp} from "../../domain/callService.schema";
import {GetRTCAccessResp} from '../../domain/callService.schema.js';
export const callHandlers = [
http.post('*/v2/chat/getRTCAccess', () => {

View File

@@ -1,5 +1,5 @@
import {http, HttpResponse} from "msw";
import {Chat, GetAvailabilityResp, StartNewReq} from "../../domain/chatService.schema";
import {Chat, GetAvailabilityResp, StartNewReq} from '../../domain/chatService.schema.js';
export const chatHandlers = [
http.get('*/chat/get', () => {

View File

@@ -1,7 +1,7 @@
import {http, HttpResponse} from "msw";
import {Chat} from "../../domain/chatService.schema";
import {FinishMessageReq, GetMessagePosResp, Message, PinnedMessage} from "../../domain/dmService.schema";
import {CreateNetworkReq, Network} from "../../domain/networkService.schema";
import {Chat} from '../../domain/chatService.schema.js';
import {FinishMessageReq, GetMessagePosResp, Message, PinnedMessage} from '../../domain/dmService.schema.js';
import {CreateNetworkReq, Network} from '../../domain/networkService.schema.js';
export const dmHandlers = [
http.get('*/chat/dm/messages', () => {
@@ -28,4 +28,8 @@ export const dmHandlers = [
message: body.message,
})
}),
http.post('*/v2/chat/dm/joinWebSocketRoom', async () => {
return HttpResponse.json()
}),
]

View File

@@ -1,6 +1,6 @@
import {http, HttpResponse} from "msw";
import {GetRTCAccessResp} from "../../domain/callService.schema";
import {RegisterUploadResp} from "../../domain/fileUploadService.schema";
import {GetRTCAccessResp} from '../../domain/callService.schema.js';
import {RegisterUploadResp} from '../../domain/fileUploadService.schema.js';
export const fileUploadHandlers = [
http.post('*/chat/cdnRegisterUpload', () => {

View File

@@ -5,8 +5,8 @@ import {
Network,
NetworkCategory,
NetworkInvite, POW
} from "../../domain/networkService.schema";
import {PublicUserData} from "../../domain/common.schema";
} from '../../domain/networkService.schema.js';
import {PublicUserData} from '../../domain/common.schema.js';
export const authHandlers = [
http.get('*/network/invites', () => {

View File

@@ -1,6 +1,6 @@
import {http, HttpResponse} from "msw";
import {CreateNetworkReq, Network, NetworkInvite} from "../../domain/networkService.schema";
import {Album, Comment, CreateAlbumReq, GetResp} from "../../domain/pictureService.schema";
import {CreateNetworkReq, Network, NetworkInvite} from '../../domain/networkService.schema.js';
import {Album, Comment, CreateAlbumReq, GetResp} from '../../domain/pictureService.schema.js';
export const pictureHandlers = [
http.get('*/picture/pictures', () => {

View File

@@ -1,6 +1,6 @@
import {http, HttpResponse} from "msw";
import {GetResp} from "../../domain/pictureService.schema";
import {Session} from "../../domain/userService.schema";
import {GetResp} from '../../domain/pictureService.schema.js';
import {Session} from '../../domain/userService.schema.js';
export const userHandler = [
http.post('*/user/getSessions', () => {

View File

@@ -1,11 +1,12 @@
import {networkHandlers} from "./handlers/auth.http";
import {authHandlers} from "./handlers/network.http";
import {pictureHandlers} from "./handlers/picture.http";
import {callHandlers} from "./handlers/call.http";
import {fileUploadHandlers} from "./handlers/fUpl.http";
import {chatHandlers} from "./handlers/chat.http";
import {dmHandlers} from "./handlers/dm.http";
import {userHandler} from "./handlers/user.http";
import {networkHandlers} from './handlers/auth.http.js';
import {authHandlers} from './handlers/network.http.js';
import {pictureHandlers} from './handlers/picture.http.js';
import {callHandlers} from './handlers/call.http.js';
import {fileUploadHandlers} from './handlers/fUpl.http.js';
import {chatHandlers} from './handlers/chat.http.js';
import {dmHandlers} from './handlers/dm.http.js';
import {userHandler} from './handlers/user.http.js';
import {brcChanHandlers} from './handlers/brcChan.http.js';
export const allHandlers = [
...authHandlers,
@@ -15,5 +16,6 @@ export const allHandlers = [
...fileUploadHandlers,
...chatHandlers,
...dmHandlers,
...userHandler
...userHandler,
...brcChanHandlers
]

View File

@@ -1,4 +1,4 @@
import {setupServer} from "msw/node";
import {allHandlers} from "./index";
import {allHandlers} from './index.js';
export const mockServer = setupServer(...allHandlers)

View File

@@ -1,9 +1,12 @@
import {DatabaseAPI} from "../../storage/database";
import {DatabaseAPI} from '../../storage/database.js';
export class DatabaseMock implements DatabaseAPI {
database: { [collection: string]: { [key: string]: string } } = {};
set(collection: string, key: string, value: any) {
if (!this.database[collection]) {
this.database[collection] = {};
}
this.database[collection][key] = JSON.stringify(value);
}

View File

@@ -1,4 +1,4 @@
import {KeyringAPI} from "../../storage/keyring";
import {KeyringAPI} from '../../storage/keyring.js';
export class KeyringMock implements KeyringAPI {
ring: { [key: string]: string } = {};
@@ -7,15 +7,15 @@ export class KeyringMock implements KeyringAPI {
this.ring[key] = value;
}
get(key: string): string {
return this.ring[key];
get(key: string): Promise<string> {
return Promise.resolve(this.ring[key]);
}
getAll(): Promise<string[]> {
return Promise.resolve(Object.keys(this.ring));
}
delete(key: string) {
delete this.ring[key];
}
flush() {
this.ring = {};
}
}

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {AuthService} from "./authService";
import {VerificationTypeEmail} from "../domain/authService.schema";
import {AuthService} from './authService.js';
import {VerificationTypeEmail} from '../domain/authService.schema.js';
import {faker} from "@faker-js/faker/locale/en";
describe("AuthService", () => {

View File

@@ -1,4 +1,4 @@
import {getClient} from "../core/http";
import {getClient} from '../core/http.js';
import {
AuthMethods, FinishPleAccountReq,
LoginPasswordAuthReq, LoginWithApple, LoginWithGoogleReq, OtpPleCodeSendTestingResp,
@@ -6,9 +6,9 @@ import {
OtpVerifyCodeReq, PleSendCodeReq,
PleVerifyCodeReq, PleVerifyCodeResp, RegisterReq, ResetPasswordReq, ResetPasswordResp,
SignInSuccessResp, UserDataValidationResp, VerifyPasswordResetReq
} from "../domain/authService.schema";
} from '../domain/authService.schema.js';
import {isAxiosError} from "axios";
import {GenericErrorBody, GenericSuccessBody} from "../domain/http.schema";
import {GenericErrorBody, GenericSuccessBody} from '../domain/http.schema.js';
export class AuthService {
/**

View File

@@ -0,0 +1,11 @@
import {describe, expect, it} from "vitest";
import {BroadcastChannelService} from './broadcastChannelService.js';
describe("BroadcastChannelService", () => {
const service = new BroadcastChannelService("", "", "", "", "", () => {})
it("should get stream registry data", async () => {
const data = await service.getData()
expect(data.status).toBe("broadcasting_starting")
})
})

View File

@@ -0,0 +1,93 @@
import {AxiosInstance, isAxiosError} from "axios";
import {getClient} from '../core/http.js';
import {MessageListener} from '../domain/websocket.schema.js';
import {WebSocketHandler} from '../core/webSocketHandler.js';
import {AcceptInviteReq} from '../domain/networkService.schema.js';
import {GenericErrorBody} from '../domain/http.schema.js';
import {
CreateServerReq,
GetRTMPDataReq,
JoinWebsocketRoomReq,
StreamRegistry
} from '../domain/broadcastChannelService.schema.js';
export class BroadcastChannelService {
userid: string
channelId: string
networkId: string
categoryId: string
client: AxiosInstance;
constructor(token: string, userid: string, networkId: string, categoryId: string, channelId: string, wsMessageListener: MessageListener) {
this.userid = userid;
this.channelId = channelId;
this.categoryId = categoryId;
this.networkId = networkId;
this.client = getClient(false).create({
headers: {
"Authorization": token,
"X-WS-ID": WebSocketHandler.getInstance().connId
}
})
WebSocketHandler.getInstance().registerService({
identifier: channelId,
onNewConnId: this.onNewConnId,
onNewMessage: wsMessageListener,
})
}
private onNewConnId(newConnId: string) {
console.log("NetworkService: New connection id")
this.client.defaults.headers["X-WS-ID"] = newConnId;
}
async getData(): Promise<StreamRegistry> {
try {
const resp = await this.client.get<StreamRegistry>(`network/channel/rtmpData?userid=${this.userid}&channelId=${this.channelId}&networkId=${this.networkId}&categoryId=${this.categoryId}`);
return resp.data
} catch (e) {
console.log(e)
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async createServer(): Promise<StreamRegistry> {
try {
const resp = await this.client.post<StreamRegistry>("network/channel/createServer", <CreateServerReq>{
userid: this.userid,
channelId: this.channelId,
networkId: this.networkId,
type: "rtmp",
categoryId: this.categoryId,
});
return resp.data
} catch (e) {
console.log(e)
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async joinWebSocketRoom(): Promise<void> {
try {
await this.client.post("v2/network/channel/joinWebSocketRoom", <JoinWebsocketRoomReq>{
userid: this.userid,
channelId: this.channelId,
networkId: this.networkId,
connId: WebSocketHandler.getInstance().connId,
categoryId: this.categoryId,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
}

View File

@@ -1,5 +1,5 @@
import {describe, expect, it} from "vitest";
import {CallService} from "./callService";
import {CallService} from './callService.js';
describe("CallService", () => {
const handler = new CallService("", "")

View File

@@ -1,8 +1,8 @@
import {getClient} from "../core/http";
import {OtpPleCodeSendTestingResp, OtpSendCodeReq} from "../domain/authService.schema";
import {getClient} from '../core/http.js';
import {OtpPleCodeSendTestingResp, OtpSendCodeReq} from '../domain/authService.schema.js';
import {isAxiosError} from "axios";
import {GenericErrorBody} from "../domain/http.schema";
import {GetRTCAccessReq, GetRTCAccessResp, InviteToCallReq} from "../domain/callService.schema";
import {GenericErrorBody} from '../domain/http.schema.js';
import {GetRTCAccessReq, GetRTCAccessResp, InviteToCallReq} from '../domain/callService.schema.js';
export class CallService {
userid: string;

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {ChatService} from "./chatService";
import {DatabaseMock} from "../mocks/storage/database";
import {ChatService} from './chatService.js';
import {DatabaseMock} from '../mocks/storage/database.js';
import {faker} from "@faker-js/faker/locale/en";
describe("ChatService", () => {

View File

@@ -1,18 +1,18 @@
import {DatabaseAPI} from "../storage/database";
import {MessageListener} from "../domain/websocket.schema";
import {getClient} from "../core/http";
import {WebSocketHandler} from "../core/webSocketHandler";
import {DatabaseAPI} from '../storage/database.js';
import {MessageListener} from '../domain/websocket.schema.js';
import {getClient} from '../core/http.js';
import {WebSocketHandler} from '../core/webSocketHandler.js';
import {AxiosInstance, isAxiosError} from "axios";
import {NetworkInvite} from "../domain/networkService.schema";
import {GenericErrorBody} from "../domain/http.schema";
import {NetworkInvite} from '../domain/networkService.schema.js';
import {GenericErrorBody} from '../domain/http.schema.js';
import {
Chat,
GetAvailabilityReq,
GetAvailabilityResp,
StartNewReq,
ToggleChatMuteReq
} from "../domain/chatService.schema";
import {Message} from "../domain/dmService.schema";
} from '../domain/chatService.schema.js';
import {Message} from '../domain/dmService.schema.js';
/**
* ChatService is an exception because it's one instance for all chats because it's unnecessary to create a new instance for each chat
@@ -58,8 +58,8 @@ export class ChatService {
}
}
getQuick(): Message[] {
const chats = this.database.get("chats", this.userid)
async getQuick(): Promise<Message[]> {
const chats = await this.database.get("chats", this.userid)
if (chats) {
return JSON.parse(chats)
} else {

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {DMService} from "./dmService";
import {DatabaseMock} from "../mocks/storage/database";
import {DMService} from './dmService.js';
import {DatabaseMock} from '../mocks/storage/database.js';
import {faker} from "@faker-js/faker/locale/en";
describe("DmService", () => {

View File

@@ -1,8 +1,8 @@
import {DatabaseAPI} from "../storage/database";
import {DatabaseAPI} from '../storage/database.js';
import {AxiosInstance, isAxiosError} from "axios";
import {MessageListener} from "../domain/websocket.schema";
import {getClient} from "../core/http";
import {WebSocketHandler} from "../core/webSocketHandler";
import {MessageListener} from '../domain/websocket.schema.js';
import {getClient} from '../core/http.js';
import {WebSocketHandler} from '../core/webSocketHandler.js';
import {
DeleteMessagesReq,
EditMessageReq,
@@ -10,11 +10,11 @@ import {
GetMessagePosResp, JoinWsRoomReq,
Message, PinMessageReq,
PinnedMessage, ReadMessagesReq, UnpinMessageReq
} from "../domain/dmService.schema";
import {NetworkInvite} from "../domain/networkService.schema";
import {GenericErrorBody} from "../domain/http.schema";
import {FileData} from "../domain/fileUploadService.schema";
import {FileUploadService} from "./fileUploadService";
} from '../domain/dmService.schema.js';
import {NetworkInvite} from '../domain/networkService.schema.js';
import {GenericErrorBody} from '../domain/http.schema.js';
import {FileData, FileUploadProgressListener} from '../domain/fileUploadService.schema.js';
import {FileUploadService} from './fileUploadService.js';
export class DMService {
userid: string;
@@ -44,6 +44,7 @@ export class DMService {
private onNewConnId(newConnId: string) {
console.log("NetworkService: New connection id")
this.client.defaults.headers["X-WS-ID"] = newConnId;
this.joinWebSocketRoom().then()
}
/**
@@ -65,8 +66,8 @@ export class DMService {
}
}
getQuick(): Message[] {
const messages = this.database.get("messages", this.chatid)
async getQuick(): Promise<Message[]> {
const messages = await this.database.get("messages", this.chatid)
if (messages) {
return JSON.parse(messages)
} else {
@@ -133,12 +134,13 @@ export class DMService {
* @param replyTo
* @param replyToMessage
* @param attachments
* @param progressListener
*/
async sendMessage(message: string, replyTo: string | null = null, replyToMessage: string | null = null, attachments: FileData[] | null = null): Promise<Message> {
async sendMessage(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)
uploadId = await uploader.uploadFiles(this.chatid, this.userid, attachments, progressListener!)
}
try {
const resp = await this.client.post<Message>("chat/dm/finishMessage", <FinishMessageReq>{
@@ -243,7 +245,7 @@ export class DMService {
*/
async joinWebSocketRoom(): Promise<void> {
try {
const resp = await this.client.patch("chat/dm/joinWebSocketRoom", <JoinWsRoomReq>{
await this.client.post("v2/chat/dm/joinWebSocketRoom", <JoinWsRoomReq>{
chatid: this.chatid,
userid: this.userid,
connId: WebSocketHandler.getInstance().connId,

View File

@@ -0,0 +1,156 @@
import {DatabaseAPI} from '../storage/database.js';
import {AxiosInstance, isAxiosError} from "axios";
import {MessageListener} from '../domain/websocket.schema.js';
import {getClient} from '../core/http.js';
import {WebSocketHandler} from '../core/webSocketHandler.js';
import {CreateNetworkReq, Network} from '../domain/networkService.schema.js';
import {GenericErrorBody} from '../domain/http.schema.js';
import {
AcceptFileTransferReq, DeclineFileTransferReq, FileTransferSendAnswerRTCReq,
FileTransferSendICERTCReq, FileTransferSendOfferRTCReq,
StartNewFileTransferReq,
StartNewFileTransferResp,
TransferableFileMetadata
} from '../domain/fileTransferService.schema.js';
export class FileTransferService {
userid: string;
peerId: string;
transferId: string = "";
client: AxiosInstance
constructor(userid: string, token: string, peerId: string) {
this.userid = userid;
this.peerId = peerId;
this.client = getClient(false).create({
headers: {
"Authorization": token,
}
})
}
/**
* Starts a new file transfer with the specified user. The new transferId will be saved automatically and not required to be provided later.
* @param transferableFiles
*/
async startNew(transferableFiles: TransferableFileMetadata[]): Promise<string> {
try {
const resp = await this.client.post<StartNewFileTransferResp>("v2/chat/dm/startNewFileTransfer", <StartNewFileTransferReq>{
userid: this.userid,
targetUserId: this.peerId,
metadata: transferableFiles
});
this.transferId = resp.data.transferId
return resp.data.transferId
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Accepts the file transfer with the specified transferId. The transferId will be saved automatically and not required to be provided later.
* @param transferId
*/
async accept(transferId: string): Promise<RTCConfiguration> {
try {
const resp = await this.client.post<RTCConfiguration>("v2/chat/dm/acceptFileTransfer", <AcceptFileTransferReq>{
userid: this.userid,
senderId: this.peerId,
transferId: transferId
});
this.transferId = transferId
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Declines the file transfer with the specified transferId.
* @param transferId
*/
async decline(transferId: string): Promise<void> {
try {
await this.client.post("v2/chat/dm/declineFileTransfer", <DeclineFileTransferReq>{
userid: this.userid,
senderId: this.peerId,
transferId: transferId
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Forwards your RTC offer to the specified user.
* @param offer
*/
async sendRtcOffer(offer: string): Promise<void> {
try {
await this.client.post("v2/chat/dm/sendRtcOfferFileTransfer", <FileTransferSendOfferRTCReq>{
userid: this.userid,
peerId: this.peerId,
transferId: this.transferId,
offer: offer,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Forwards your RTC answer to the specified user.
* @param answer
*/
async sendRtcAnswer(answer: string): Promise<void> {
try {
await this.client.post("v2/chat/dm/sendRtcAnswerFileTransfer", <FileTransferSendAnswerRTCReq>{
userid: this.userid,
peerId: this.peerId,
transferId: this.transferId,
answer: answer,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Forwards your RTC ICE candidate to the specified user.
* @param candidate
*/
async sendRtcICE(candidate: string): Promise<void> {
try {
await this.client.post("v2/chat/dm/sendRtcICEFileTransfer", <FileTransferSendICERTCReq>{
userid: this.userid,
peerId: this.peerId,
transferId: this.transferId,
candidate: candidate,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
}

View File

@@ -1,10 +1,12 @@
import {describe, expect, it} from "vitest";
import {FileUploadService} from "./fileUploadService";
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

@@ -1,14 +1,14 @@
import {
ChunkUploadReq,
FileData,
FileData, FileUploadProgressListener,
FileUploadRegistration, FinishUploadReq,
RegisterUploadReq,
RegisterUploadResp
} from "../domain/fileUploadService.schema";
} from '../domain/fileUploadService.schema.js';
import {AxiosInstance, isAxiosError} from "axios";
import {getClient} from "../core/http";
import {InviteToCallReq} from "../domain/callService.schema";
import {GenericErrorBody} from "../domain/http.schema";
import {getClient} from '../core/http.js';
import {InviteToCallReq} from '../domain/callService.schema.js';
import {GenericErrorBody} from '../domain/http.schema.js';
import {v4 as uuidv4} from 'uuid';
export class FileUploadService {
@@ -42,13 +42,14 @@ export class FileUploadService {
* @param roomId chatid or channelId
* @param userid
* @param files
* @param listener
*/
async uploadFiles(roomId: string, userid: string, files: FileData[]): Promise<string> {
async uploadFiles(roomId: string, userid: string, files: FileData[], listener: FileUploadProgressListener): Promise<string> {
let registrations: FileUploadRegistration[] = [];
files.forEach(file => {
registrations.push({
fileId: uuidv4(),
fileId: file.fileId,
name: file.name,
type: file.type,
size: file.data.size,
@@ -62,7 +63,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])
await this.uploadFile(resp.data.uploadId, roomId, userid, files[filesUploaded], registrations[filesUploaded], listener)
}
await this.finishUpload(roomId, userid, resp.data.uploadId)
return resp.data.uploadId
@@ -92,7 +93,7 @@ export class FileUploadService {
}
}
private async uploadFile(uploadId: string, roomId: string, userid: string, file: FileData, registration: FileUploadRegistration): Promise<void> {
private async uploadFile(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);
@@ -104,6 +105,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)
}
}

View File

@@ -1,9 +1,9 @@
import {describe, expect, it} from "vitest";
import {NetworkService} from "./networkService";
import {DatabaseMock} from "../mocks/storage/database";
import {NetworkService} from './networkService.js';
import {DatabaseMock} from '../mocks/storage/database.js';
import {faker} from "@faker-js/faker/locale/en";
import {getClient} from "../core/http";
import {environment, SDKConfig} from "../core/environment";
import {getClient} from '../core/http.js';
import {environment, SDKConfig} from '../core/environment.js';
describe("NetworkService", () => {
const service = new NetworkService("", "", "", new DatabaseMock(), (action, data) => {})

View File

@@ -1,7 +1,7 @@
import {DatabaseAPI} from "../storage/database";
import {getClient} from "../core/http";
import {DatabaseAPI} from '../storage/database.js';
import {getClient} from '../core/http.js';
import {AxiosInstance, isAxiosError} from "axios";
import {GenericErrorBody} from "../domain/http.schema";
import {GenericErrorBody} from '../domain/http.schema.js';
import {
AcceptInviteReq, AssignRankToMemberReq, BanMemberReq, ChangeVisibilityReq, CreateCategoryReq, CreateChannelReq,
CreateInviteReq,
@@ -15,11 +15,11 @@ import {
NetworkInvite, NetworkRank, OverwriteChannelPermissionReq, OverwritePermissionReq, PermissionUpdate, POW,
RemoveRankFromMemberReq, ToggleCategoryMuteReq, ToggleChannelNetworkMuteReq, ToggleNetworkMuteReq,
UnbanMemberReq, UploadNewPictureReq
} from "../domain/networkService.schema";
import {PublicUserData, RGB} from "../domain/common.schema";
import {WebSocketHandler} from "../core/webSocketHandler";
import {MessageListener} from "../domain/websocket.schema";
import {Message} from "../domain/dmService.schema";
} from '../domain/networkService.schema.js';
import {PublicUserData, RGB} from '../domain/common.schema.js';
import {WebSocketHandler} from '../core/webSocketHandler.js';
import {MessageListener} from '../domain/websocket.schema.js';
import {Message} from '../domain/dmService.schema.js';
export class NetworkService {
userid: string;
@@ -108,8 +108,8 @@ export class NetworkService {
}
}
getQuick(): Message[] {
const networks = this.database.get("networks", this.userid)
async getQuick(): Promise<Message[]> {
const networks = await this.database.get("networks", this.userid)
if (networks) {
return JSON.parse(networks)
} else {

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {PictureService} from "./pictureService";
import {DatabaseMock} from "../mocks/storage/database";
import {PictureService} from './pictureService.js';
import {DatabaseMock} from '../mocks/storage/database.js';
import {faker} from "@faker-js/faker/locale/en";
describe("PictureService", () => {

View File

@@ -1,8 +1,8 @@
import {DatabaseAPI} from "../storage/database";
import {DatabaseAPI} from '../storage/database.js';
import {AxiosInstance, isAxiosError} from "axios";
import {getClient} from "../core/http";
import {NetworkInvite} from "../domain/networkService.schema";
import {GenericErrorBody} from "../domain/http.schema";
import {getClient} from '../core/http.js';
import {NetworkInvite} from '../domain/networkService.schema.js';
import {GenericErrorBody} from '../domain/http.schema.js';
import {
Album, ChangePictureVisibilityReq, Comment,
CreateAlbumReq,
@@ -10,9 +10,9 @@ import {
DiscoveryResp, EditPictureTitleReq,
FinalizeUploadReq,
GetResp, PostCommentReq, ToggleFollowReq, TogglePictureLikeReq, UploadImageReq
} from "../domain/pictureService.schema";
import {environment} from "../core/environment";
import {Message} from "../domain/dmService.schema";
} from '../domain/pictureService.schema.js';
import {environment} from '../core/environment.js';
import {Message} from '../domain/dmService.schema.js';
export class PictureService {
userid: string;
@@ -54,8 +54,8 @@ export class PictureService {
}
}
getQuick(): Message[] {
const pictures = this.database.get("pictures", this.uploaderId)
async getQuick(): Promise<Message[]> {
const pictures = await this.database.get("pictures", this.uploaderId)
if (pictures) {
return JSON.parse(pictures)
} else {

View File

@@ -0,0 +1,134 @@
import {PublicUserData} from '../domain/common.schema.js';
import {DatabaseAPI} from '../storage/database.js';
import {KeyringAPI} from '../storage/keyring.js';
import {KeyValueAPI} from '../storage/keyvalue.js';
import {Session, ValidateSessionReq, ValidateSessionResp} from '../domain/sessionManager.schema.js';
import {AxiosInstance} from "axios";
import {getClient} from '../core/http.js';
import {PersonalUserData} from '../domain/userService.schema.js';
export class SessionManager {
client: AxiosInstance;
database: DatabaseAPI;
keyring: KeyringAPI;
KeyValue: KeyValueAPI;
constructor(database: DatabaseAPI, keyring: KeyringAPI, KeyValue: KeyValueAPI) {
this.database = database;
this.keyring = keyring;
this.KeyValue = KeyValue;
this.client = getClient(false)
}
/**
* Saves the new session to the database and the keyring
* @param userData
* @param token
*/
addSession(userData: PublicUserData, token: string): void {
this.database.set("sessions", userData.userid, JSON.stringify(userData))
this.keyring.set(userData.userid, token)
}
/**
* Loads all saved sessions
*/
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)
})
}
}
return sessions
}
/**
* Gets the preferred user set by the client
*/
async getPreferredUser(): Promise<string> {
return await this.KeyValue.get("preferredUser") ?? ""
}
/**
* Sets a new preferred user
* @param userid
*/
setPreferredUser(userid: string): void {
this.KeyValue.set("preferredUser", userid)
}
/**
* Loads the preferred session by the client
*/
async loadPreferredSession() {
const sessions = await this.loadSessions()
let preferredUser = await this.getPreferredUser()
if (preferredUser == "") {
preferredUser = sessions[0].userData.userid
this.setPreferredUser(sessions[0].userData.userid)
}
const preferredSession = sessions.find(s => s.userData.userid == preferredUser)
if (preferredSession) {
return preferredSession
} else {
return sessions[0]
}
}
/**
* 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)
if (!await this.validateSession(session.token)) {
this.database.delete("sessions", session.userData.userid)
this.keyring.delete(session.userData.userid)
sessions.splice(index, 1)
}
const updatedUserData = await this.updateUserData(session)
this.database.set("sessions", session.userData.userid, updatedUserData)
sessions[index] = updatedUserData
})
return sessions
}
private async validateSession(token: string): Promise<boolean> {
try {
const resp = await this.client.post<ValidateSessionResp>("v2/user/validateSession", <ValidateSessionReq>{
token: token,
})
return resp.data.validationOk
} catch (e) {
return true
}
}
private async updateUserData(session: Session): Promise<Session> {
const authenticatedClient = this.client.create({
headers: {
"Authorization": session.token,
}
})
try {
const resp = await authenticatedClient.get<PersonalUserData>(`user/byUseridPersonal?userid=${session.userData.userid}`)
session.userData = resp.data
return session
} catch (e) {
throw new Error("Session update error")
}
}
}

View File

@@ -0,0 +1,280 @@
import {DatabaseAPI} from '../storage/database.js';
import {AxiosInstance, isAxiosError} from "axios";
import {MessageListener} from '../domain/websocket.schema.js';
import {getClient} from '../core/http.js';
import {WebSocketHandler} from '../core/webSocketHandler.js';
import {
DeleteMessagesReq,
EditMessageReq,
FinishMessageReq,
GetMessagePosResp, JoinWsRoomReq,
Message, PinMessageReq,
PinnedMessage, ReadMessagesReq, UnpinMessageReq
} from '../domain/textChannelService.schema.js';
import {NetworkInvite} from '../domain/networkService.schema.js';
import {GenericErrorBody} from '../domain/http.schema.js';
import {FileData, FileUploadProgressListener} from '../domain/fileUploadService.schema.js';
import {FileUploadService} from './fileUploadService.js';
export class TextChannelServiceService {
userid: string;
networkId: string;
categoryId: string;
channelId: string;
token: string;
database: DatabaseAPI;
client: AxiosInstance
constructor(userid: string, token: string, networkId: string, categoryId: string, channelId: string, database: DatabaseAPI, wsMessageListener: MessageListener) {
this.userid = userid;
this.networkId = networkId;
this.categoryId = categoryId;
this.channelId = channelId;
this.database = database;
this.token = token;
this.client = getClient(false).create({
headers: {
"Authorization": token,
"X-WS-ID": WebSocketHandler.getInstance().connId
}
})
WebSocketHandler.getInstance().registerService({
identifier: channelId,
onNewConnId: this.onNewConnId,
onNewMessage: wsMessageListener,
})
}
private onNewConnId(newConnId: string) {
console.log("NetworkService: New connection id")
this.client.defaults.headers["X-WS-ID"] = newConnId;
}
/**
* Fetches all messages in the chat
* @param from
*/
async get(from: number = 0): Promise<Message[]> {
try {
const resp = await this.client.get<Message[]>(`network/channel/messages?networkId=${this.networkId}&channelId=${this.channelId}&categoryId=${this.categoryId}&userid=${this.userid}&from=${from}`);
if (from == 0) {
this.database.set("networkmessages", this.channelId, JSON.stringify(resp.data))
}
console.log(resp.data, "ASD")
return resp.data
} catch (e) {
console.log(e)
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async getQuick(): Promise<Message[]> {
const messages = await this.database.get("networkmessages", this.channelId)
if (messages) {
return JSON.parse(messages)
} else {
throw new Error("No messages in database")
}
}
/**
* Fetches the position of the specified message which can be used in get() to fetch an old message with it's surrounding messages
* @param messageId
*/
async getMessagePos(messageId: string): Promise<number> {
try {
const resp = await this.client.get<GetMessagePosResp>(`network/channel/getMessagePosition?networkId=${this.networkId}&channelId=${this.channelId}&categoryId=${this.categoryId}&userid=${this.userid}&messageId=${messageId}`);
return resp.data.messagePos
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Gets all messages pinned in the chat
*/
async getPinnedMessages(): Promise<PinnedMessage[]> {
try {
const resp = await this.client.get<PinnedMessage[]>(`network/channel/pinnedMessages?networkId=${this.networkId}&channelId=${this.channelId}&categoryId=${this.categoryId}&userid=${this.userid}`);
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Edits the specified message
* @param messageId
* @param newMessage
*/
async editMessage(messageId: string, newMessage: string): Promise<void> {
try {
const resp = await this.client.patch("network/channel/editMessage", <EditMessageReq>{
messageId: messageId,
networkId: this.networkId,
channelId: this.channelId,
categoryId: this.categoryId,
userid: this.userid,
message: newMessage
});
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Sends a new message to the chat
* @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> {
let uploadId = ""
if (attachments) {
const uploader = new FileUploadService(this.token)
uploadId = await uploader.uploadFiles(this.channelId, this.userid, attachments, progressListener!)
}
try {
const resp = await this.client.post<Message>("network/channel/finishMessage", <FinishMessageReq>{
message: message,
networkId: this.networkId,
channelId: this.channelId,
categoryId: this.categoryId,
replyTo: replyTo,
replyToMessage: replyToMessage,
userid: this.userid,
uploadId: uploadId,
});
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Reads all messages sent to you in the chat
*/
async readMessages(): Promise<void> {
try {
await this.client.patch("network/channel/readMessages", <ReadMessagesReq>{
networkId: this.networkId,
channelId: this.channelId,
categoryId: this.categoryId,
userid: this.userid,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Pins the specified message
* @param messageId
* @param message
*/
async pinMessage(messageId: string, message: string): Promise<void> {
try {
const resp = await this.client.patch("network/channel/pinMessage", <PinMessageReq>{
messageId: messageId,
networkId: this.networkId,
channelId: this.channelId,
categoryId: this.categoryId,
userid: this.userid,
message: message
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Unpins the specified message
* @param messageId
*/
async unpinMessage(messageId: string): Promise<void> {
try {
const resp = await this.client.patch("network/channel/unpinMessage", <UnpinMessageReq>{
messageId: messageId,
networkId: this.networkId,
channelId: this.channelId,
categoryId: this.categoryId,
userid: this.userid,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Deletes the message(s)
* @param messageIds
*/
async deleteMessages(messageIds: string[]): Promise<void> {
try {
const resp = await this.client.patch("network/channel/deleteMessages", <DeleteMessagesReq>{
networkId: this.networkId,
channelId: this.channelId,
categoryId: this.categoryId,
userid: this.userid,
messageIds: messageIds
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
/**
* Joins the WebSocket room to start receiving realtime messages
*/
async joinWebSocketRoom(): Promise<void> {
try {
const resp = await this.client.patch("network/channel/joinWebSocketRoom", <JoinWsRoomReq>{
networkId: this.networkId,
channelId: this.channelId,
categoryId: this.categoryId,
userid: this.userid,
connId: WebSocketHandler.getInstance().connId,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
}

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {UserService} from "./userService";
import {DatabaseMock} from "../mocks/storage/database";
import {UserService} from './userService.js';
import {DatabaseMock} from '../mocks/storage/database.js';
describe("UserService", () => {
const service = new UserService("", "", new DatabaseMock())

View File

@@ -1,10 +1,10 @@
import {DatabaseAPI} from "../storage/database";
import {DatabaseAPI} from '../storage/database.js';
import {AxiosInstance, isAxiosError} from "axios";
import {MessageListener} from "../domain/websocket.schema";
import {getClient} from "../core/http";
import {WebSocketHandler} from "../core/webSocketHandler";
import {DeleteCategoryReq} from "../domain/networkService.schema";
import {GenericErrorBody} from "../domain/http.schema";
import {MessageListener} from '../domain/websocket.schema.js';
import {getClient} from '../core/http.js';
import {WebSocketHandler} from '../core/webSocketHandler.js';
import {DeleteCategoryReq} from '../domain/networkService.schema.js';
import {GenericErrorBody} from '../domain/http.schema.js';
import {
ChangeDisplayNameReq,
ChangeEmailReq,
@@ -12,9 +12,9 @@ import {
ChangeUsernameReq, CurrNewCodeTestingResp, DeleteReq, GetSessionsReq, GIF, RegisterFCMTokenReq, Session,
ToggleGifSaveReq, UploadNewPfpCdnReq, UploadNewPfpCdnResp,
UploadNewPfpReq, VerifyMailChangeReq, VerifyPhoneChange
} from "../domain/userService.schema";
import {RGB} from "../domain/common.schema";
import {OtpPleCodeSendTestingResp} from "../domain/authService.schema";
} from '../domain/userService.schema.js';
import {RGB} from '../domain/common.schema.js';
import {OtpPleCodeSendTestingResp} from '../domain/authService.schema.js';
export class UserService {
userid: string;

View File

@@ -1,6 +1,6 @@
export interface DatabaseAPI {
set(collection: string, key: string, value: any): void;
get(collection: string, key: string): string;
get(collection: string, key: string): Promise<string>;
delete(collection: string, key: string): void;
flush(): void;
}

View File

@@ -1,6 +1,6 @@
export interface KeyringAPI {
set(key: string, value: any): void;
get(key: string): string;
get(key: string): Promise<string>;
getAll(): Promise<string[]>;
delete(key: string): void;
flush(): void;
}

6
src/storage/keyvalue.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface KeyValueAPI {
set(key: string, value: any): void;
get(key: string): Promise<string>;
delete(key: string): void;
flush(): void;
}

View File

@@ -1,5 +1,5 @@
import {beforeAll, afterEach, afterAll, vi} from 'vitest';
import {mockServer} from "./mocks/node";
import {mockServer} from './mocks/node.js';
beforeAll(() => mockServer.listen({onUnhandledRequest: 'error'}));
afterEach(() => mockServer.resetHandlers());

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {AuthService} from "../src/services/authService";
import {VerificationTypeEmail} from "../src/domain/authService.schema";
import {AuthService} from '../src/services/authService.js';
import {VerificationTypeEmail} from '../src/domain/authService.schema.js';
describe("AuthService", () => {
it("should return authMethods", async () => {

View File

@@ -0,0 +1,29 @@
import {describe, expect, it} from "vitest";
import {BroadcastChannelService} from '../src/services/broadcastChannelService.js';
const BRC_CHAN_SERVICE_TESTING_NETWORK_ID = "000000000000000000000000"
const BRC_CHAN_SERVICE_TESTING_USER_ID = "000000000000000000000000"
const BRC_CHAN_SERVICE_TESTING_CHANNEL_ID = "333333333333333333333333"
const BRC_CHAN_SERVICE_TESTING_TOKEN = "testingToken"
const BRC_CHAN_SERVICE_TESTING_CATEGORY_ID = "111111111111111111111111"
describe("BroadcastChannelService Integration Testing", () => {
const service = new BroadcastChannelService(
BRC_CHAN_SERVICE_TESTING_TOKEN,
BRC_CHAN_SERVICE_TESTING_USER_ID,
BRC_CHAN_SERVICE_TESTING_NETWORK_ID,
BRC_CHAN_SERVICE_TESTING_CATEGORY_ID,
BRC_CHAN_SERVICE_TESTING_CHANNEL_ID,
(action, data) => {}
)
it('should create a new server and fetch it', async () => {
await service.createServer()
const registry = await service.getData()
expect(registry.status).toBe("idling")
});
it('should join ws room', async () => {
await service.joinWebSocketRoom()
});
})

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {DatabaseMock} from "../src/mocks/storage/database";
import {ChatService} from "../src/services/chatService";
import {DatabaseMock} from '../src/mocks/storage/database.js';
import {ChatService} from '../src/services/chatService.js';
describe("ChatService Integration Testing", () => {
const CHAT_SERVICE_TESTING_USER_ID = "000000000000000000000000"

View File

@@ -1,7 +1,7 @@
import {describe, expect, it} from "vitest";
import {DMService} from "../src/services/dmService";
import {ChatService} from "../src/services/chatService";
import {DatabaseMock} from "../src/mocks/storage/database";
import {DMService} from '../src/services/dmService.js';
import {ChatService} from '../src/services/chatService.js';
import {DatabaseMock} from '../src/mocks/storage/database.js';
import {faker} from "@faker-js/faker/locale/en";
describe("DmService Integration Testing", () => {

View File

@@ -1,9 +1,10 @@
import {describe, it} from "vitest";
import {FileUploadService} from "../src/services/fileUploadService";
import {environment, SDKConfig} from "../src/core/environment";
import {getClient} from "../src/core/http";
import {FileData} from "../src/domain/fileUploadService.schema";
import {FileUploadService} from '../src/services/fileUploadService.js';
import {environment, SDKConfig} from '../src/core/environment.js';
import {getClient} from '../src/core/http.js';
import {FileData} from '../src/domain/fileUploadService.schema.js';
import axios from "axios";
import {v4 as uuidv4} from 'uuid';
describe("FileUploadService Integration Testing", () => {
const FILE_UPL_SERVICE_TESTING_USER_ID = "000000000000000000000000"
@@ -21,12 +22,16 @@ describe("FileUploadService Integration Testing", () => {
FILE_UPL_SERVICE_TESTING_USER_ID,
[
{
fileId: uuidv4(),
name: "filename",
type: "image",
extension: "jpeg",
data: new File([response.data], "filename", { type: "image/jpeg" })
}
]
],
{
fileProgressUpdate: () => {},
}
)
});
})

View File

@@ -1,12 +1,12 @@
import {describe, expect, it} from "vitest";
import {NetworkService} from "../src/services/networkService";
import {DatabaseMock} from "../src/mocks/storage/database";
import {environment, SDKConfig} from "../src/core/environment";
import {getClient} from "../src/core/http";
import {NetworkService} from '../src/services/networkService.js';
import {DatabaseMock} from '../src/mocks/storage/database.js';
import {environment, SDKConfig} from '../src/core/environment.js';
import {getClient} from '../src/core/http.js';
import {faker} from "@faker-js/faker/locale/en";
import {AuthService} from "../src/services/authService";
import {RGB} from "../src/domain/common.schema";
import {NetworkPermissions, PermissionUpdate} from "../src/domain/networkService.schema";
import {AuthService} from '../src/services/authService.js';
import {RGB} from '../src/domain/common.schema.js';
import {NetworkPermissions, PermissionUpdate} from '../src/domain/networkService.schema.js';
const NETWORK_SERVICE_TESTING_NETWORK_ID = "000000000000000000000000"
const NETWORK_SERVICE_TESTING_USER_ID = "000000000000000000000000"
@@ -56,12 +56,6 @@ describe("NetworkService Integration Testing", () => {
expect(category.name).toBe(catName)
});
it('should delete category', async () => {
await service.deleteCategory(NETWORK_SERVICE_TESTING_CATEGORY_ID)
const networks = await service.get()
expect(networks[0].categories.length).toBe(0)
});
it('should move category', async () => {
await service.createCategory("Test name", "Test desc")
await service.moveCategory(0, 1)
@@ -74,7 +68,7 @@ describe("NetworkService Integration Testing", () => {
});
it('should create rank', async () => {
const rankName = faker.internet.displayName()
const rankName = faker.internet.displayName().substring(0, 10)
const rank = await service.createRank(rankName, <RGB>{r: 0, g: 0, b: 0}, null)
expect(rank.name).toBe(rankName)
});

View File

@@ -1,9 +1,9 @@
import {describe, expect, it} from "vitest";
import {environment, SDKConfig} from "../src/core/environment";
import {NetworkService} from "../src/services/networkService";
import {DatabaseMock} from "../src/mocks/storage/database";
import {getClient} from "../src/core/http";
import {PictureService} from "../src/services/pictureService";
import {environment, SDKConfig} from '../src/core/environment.js';
import {NetworkService} from '../src/services/networkService.js';
import {DatabaseMock} from '../src/mocks/storage/database.js';
import {getClient} from '../src/core/http.js';
import {PictureService} from '../src/services/pictureService.js';
import {faker} from "@faker-js/faker/locale/en";
const PICTURE_SERVICE_TESTING_TOKEN = "testingToken"
@@ -24,7 +24,7 @@ describe("PictureService Integration Test", () => {
});
it('should create an album', async () => {
const albumName = faker.internet.displayName()
const albumName = faker.internet.displayName().substring(0, 10)
await service.createAlbum(albumName)
const uploads = await service.get()
expect(uploads.pictures[1].name).toBe(albumName)

View File

@@ -0,0 +1,66 @@
import {describe, expect, it} from "vitest";
import {DMService} from '../src/services/dmService.js';
import {ChatService} from '../src/services/chatService.js';
import {DatabaseMock} from '../src/mocks/storage/database.js';
import {faker} from "@faker-js/faker/locale/en";
import {TextChannelServiceService} from '../src/services/textChannelService.js';
describe("DmService Integration Testing", () => {
const TXT_CHAN_SERVICE_TESTING_CHANNEL_ID = "222222222222222222222222"
const TXT_CHAN_SERVICE_TESTING_USER_ID = "000000000000000000000000"
const TXT_CHAN_SERVICE_TESTING_MESSAGE_ID = "111111111111111111111111"
const TXT_CHAN_SERVICE_TESTING_TOKEN = "testingToken"
const NETWORK_SERVICE_TESTING_NETWORK_ID = "000000000000000000000000"
const NETWORK_SERVICE_TESTING_CATEGORY_ID = "111111111111111111111111"
const service = new TextChannelServiceService(
TXT_CHAN_SERVICE_TESTING_USER_ID,
TXT_CHAN_SERVICE_TESTING_TOKEN,
NETWORK_SERVICE_TESTING_NETWORK_ID,
NETWORK_SERVICE_TESTING_CATEGORY_ID,
TXT_CHAN_SERVICE_TESTING_CHANNEL_ID,
new DatabaseMock(),
() => {}
)
it('should get messages', async () => {
const messages = await service.get()
expect(messages[0].message).toBe("This is a message")
expect(messages[0].isEdited).toBeTruthy()
});
it('should get message position', async () => {
const pos = await service.getMessagePos(TXT_CHAN_SERVICE_TESTING_MESSAGE_ID)
expect(pos).toBe(0)
});
it('should edit message', async () => {
const newMessage = faker.lorem.paragraph()
await service.editMessage(TXT_CHAN_SERVICE_TESTING_MESSAGE_ID, newMessage)
const messages = await service.get()
expect(messages[0].message).toBe(newMessage)
});
it('should send a message', async () => {
const message = faker.lorem.paragraph()
const newMessage = await service.sendMessage(message)
expect(newMessage.message).toBe(message)
});
it('should read messages', async () => {
await service.readMessages()
});
it('should pin and unpin messages', async () => {
let pinnedMessages = await service.getPinnedMessages()
expect(pinnedMessages.length).toBe(0)
await service.pinMessage(TXT_CHAN_SERVICE_TESTING_MESSAGE_ID, "message")
pinnedMessages = await service.getPinnedMessages()
expect(pinnedMessages.length).toBe(1)
await service.unpinMessage(TXT_CHAN_SERVICE_TESTING_MESSAGE_ID)
pinnedMessages = await service.getPinnedMessages()
expect(pinnedMessages.length).toBe(0)
});
})

View File

@@ -1,7 +1,7 @@
import {describe, expect, it} from "vitest";
import {UserService} from "../src/services/userService";
import {DatabaseMock} from "../src/mocks/storage/database";
import {RGB} from "../src/domain/common.schema"
import {UserService} from '../src/services/userService.js';
import {DatabaseMock} from '../src/mocks/storage/database.js';
import {RGB} from '../src/domain/common.schema.js'
const USER_SERVICE_TESTING_USER_ID = "000000000000000000000000"
const USER_SERVICE_TESTING_TOKEN = "testingToken"

View File

@@ -1,6 +1,6 @@
import {beforeAll, beforeEach} from 'vitest';
import {environment, SDKConfig} from "../src/core/environment";
import {getClient} from "../src/core/http";
import {environment, SDKConfig} from '../src/core/environment.js';
import {getClient} from '../src/core/http.js';
beforeEach(async () => {
await getClient(false).post("v2/reset")

View File

@@ -1,5 +1,5 @@
import {describe, it} from "vitest";
import {WebSocketHandler} from "../src/core/webSocketHandler";
import {WebSocketHandler} from '../src/core/webSocketHandler.js';
const WEBSOCKET_HANDLER_TESTING_USER_ID = "000000000000000000000000"
const WEBSOCKET_HANDLER_TESTING_USER_TOKEN = "testingToken"

View File

@@ -1,12 +1,15 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"module": "NodeNext",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
"outDir": "dist",
"verbatimModuleSyntax": false,
},
"include": ["src"]
}