Compare commits
52 Commits
e9eb95e9b7
...
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 | |||
| 96a5e5896b | |||
| 14fe7ef41d | |||
| d04bd6a437 | |||
| 0e553767b6 | |||
| b7af5497a4 | |||
| 1cd629e3c1 | |||
| 7d50692ece | |||
| d7422efcf0 | |||
| 1ccf534e04 | |||
| 7ba341203d | |||
| 54d466fb27 | |||
| 07d30f7e83 | |||
| cd806b7d17 | |||
| 7daf542961 | |||
| f911224847 | |||
| c8d2de9f9d | |||
| f8de78f3ab | |||
| e07114b1c0 | |||
| e8c6b9c920 | |||
| 0a97a2cc48 | |||
| 0b38b002df | |||
| a9322e3454 | |||
| e6798b4be8 | |||
| 55e4aad0a9 | |||
| 4f96b1d687 | |||
| aa451e4786 |
30
.gitea/workflows/publish.yml
Normal file
30
.gitea/workflows/publish.yml
Normal 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
|
||||
@@ -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
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,4 +13,5 @@ yarn-error.log*
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.example
|
||||
/dist
|
||||
6
README.md
Normal file
6
README.md
Normal 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
|
||||
```
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -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",
|
||||
|
||||
29
package.json
29
package.json
@@ -1,8 +1,28 @@
|
||||
{
|
||||
"name": "chatenium-sdk",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"name": "@chatenium/chatenium-sdk",
|
||||
"version": "1.2.1",
|
||||
"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,11 +35,10 @@
|
||||
"typescript": "^5.5.3",
|
||||
"vitest": "^4.1.2"
|
||||
},
|
||||
"private": true,
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^10.4.0",
|
||||
"axios": "^1.14.0",
|
||||
"dotenv": "^17.4.0",
|
||||
"msw": "^2.12.14",
|
||||
"uuid": "^13.0.0"
|
||||
}
|
||||
|
||||
@@ -4,17 +4,13 @@ export interface SDKConfig {
|
||||
wsUrl: string;
|
||||
}
|
||||
|
||||
declare const process: 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];
|
||||
|
||||
@@ -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();
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
29
src/domain/broadcastChannelService.schema.ts
Normal file
29
src/domain/broadcastChannelService.schema.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
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
|
||||
disableAutoRemove: boolean
|
||||
}
|
||||
|
||||
export interface StreamRegistry {
|
||||
streamKey: string
|
||||
status: "idling" | "broadcasting" | "broadcasting_starting"
|
||||
type: "rtmp"
|
||||
streamURL: string
|
||||
channelId: string
|
||||
}
|
||||
@@ -24,4 +24,5 @@ export interface Attachment {
|
||||
path: string
|
||||
height: number
|
||||
width: number
|
||||
extraMetaData: Record<string, any> // Used by clients
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Attachment, TimeStamp} from "./common.schema";
|
||||
import {Attachment, TimeStamp} from './common.schema.js';
|
||||
|
||||
export interface GetMessageReq {
|
||||
from: number
|
||||
@@ -58,6 +58,7 @@ export interface JoinWsRoomReq {
|
||||
connId: string
|
||||
chatid: string
|
||||
userid: string
|
||||
disableAutoRemove: boolean
|
||||
}
|
||||
|
||||
// Response schemas
|
||||
|
||||
95
src/domain/fileTransferService.schema.ts
Normal file
95
src/domain/fileTransferService.schema.ts
Normal 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
|
||||
}
|
||||
@@ -33,8 +33,13 @@ export interface FileUploadRegistration {
|
||||
}
|
||||
|
||||
export interface FileData {
|
||||
fileId: string
|
||||
name: string
|
||||
extension: string
|
||||
type: string
|
||||
data: File
|
||||
}
|
||||
|
||||
export interface FileUploadProgressListener {
|
||||
fileProgressUpdate: (tempMsgId: string, fileId: string, allChunks: number, chunksDone: number) => void
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {PublicUserData, TimeStamp} from "./common.schema";
|
||||
import {PublicUserData, TimeStamp} from './common.schema.js';
|
||||
|
||||
// Request schemas
|
||||
export interface GetReq {
|
||||
|
||||
19
src/domain/sessionManager.schema.ts
Normal file
19
src/domain/sessionManager.schema.ts
Normal 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
|
||||
}
|
||||
139
src/domain/textChannelService.schema.ts
Normal file
139
src/domain/textChannelService.schema.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
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
|
||||
disableAutoRemove: boolean
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
114
src/domain/userService.schema.ts
Normal file
114
src/domain/userService.schema.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import {TimeStamp} from './common.schema.js';
|
||||
|
||||
export interface ChangeUsernameReq {
|
||||
newUsername: string;
|
||||
userid: string;
|
||||
}
|
||||
|
||||
export interface ChangeDisplayNameReq {
|
||||
newDisplayName: string;
|
||||
userid: string;
|
||||
}
|
||||
|
||||
export interface ChangePasswordReq {
|
||||
newPassword: string;
|
||||
currentPassword: string;
|
||||
userid: string;
|
||||
}
|
||||
|
||||
export interface ChangeEmailReq {
|
||||
currentPassword: string;
|
||||
newMail: string;
|
||||
userid: string;
|
||||
}
|
||||
|
||||
export interface VerifyMailChangeReq {
|
||||
userid: string;
|
||||
vCodeCurrent: number;
|
||||
vCodeNew: number;
|
||||
newAddress: string;
|
||||
}
|
||||
|
||||
export interface ChangePhoneReq {
|
||||
currentPassword: string;
|
||||
newPhone: string;
|
||||
userid: string;
|
||||
}
|
||||
|
||||
export interface VerifyPhoneChange {
|
||||
userid: string;
|
||||
vCodeCurrent: number;
|
||||
vCodeNew: number;
|
||||
newPhone: string;
|
||||
}
|
||||
|
||||
export interface UploadNewPfpReq {
|
||||
userid: string;
|
||||
pfpId: string;
|
||||
}
|
||||
|
||||
export interface UploadNewPfpCdnReq {
|
||||
userid: string;
|
||||
data: string | null;
|
||||
isImage: boolean;
|
||||
monogramLetter: string | null;
|
||||
monogramColors: string | null;
|
||||
}
|
||||
|
||||
export interface DeleteReq {
|
||||
userid: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterFCMTokenReq {
|
||||
userid: string;
|
||||
token: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
export interface GetSessionsReq {
|
||||
userid: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserDataReq {
|
||||
userid: string;
|
||||
}
|
||||
|
||||
export interface ToggleGifSaveReq {
|
||||
userid: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface UploadNewPfpCdnResp {
|
||||
pfpId: string;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
token: string;
|
||||
os: string;
|
||||
language: string;
|
||||
login_at: TimeStamp | string;
|
||||
}
|
||||
|
||||
export interface GIF {
|
||||
gifId: string;
|
||||
url: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface CurrNewCodeTestingResp {
|
||||
codeCurr: number|null;
|
||||
codeNew: number|null;
|
||||
}
|
||||
|
||||
export interface PersonalUserData {
|
||||
userid: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
pfp: string;
|
||||
pictureDiscovery: boolean;
|
||||
gifs: GIF[];
|
||||
passwordSet: boolean;
|
||||
emailSet: boolean;
|
||||
phoneSet: boolean;
|
||||
}
|
||||
@@ -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', () => {
|
||||
|
||||
11
src/mocks/handlers/brcChan.http.ts
Normal file
11
src/mocks/handlers/brcChan.http.ts
Normal 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"
|
||||
})
|
||||
}),
|
||||
]
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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()
|
||||
}),
|
||||
]
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
11
src/mocks/handlers/user.http.ts
Normal file
11
src/mocks/handlers/user.http.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import {http, HttpResponse} from "msw";
|
||||
import {GetResp} from '../../domain/pictureService.schema.js';
|
||||
import {Session} from '../../domain/userService.schema.js';
|
||||
|
||||
export const userHandler = [
|
||||
http.post('*/user/getSessions', () => {
|
||||
return HttpResponse.json(<Session[]>[{
|
||||
token: "sessionToken"
|
||||
}])
|
||||
}),
|
||||
]
|
||||
@@ -1,10 +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 {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,
|
||||
@@ -13,5 +15,7 @@ export const allHandlers = [
|
||||
...callHandlers,
|
||||
...fileUploadHandlers,
|
||||
...chatHandlers,
|
||||
...dmHandlers
|
||||
...dmHandlers,
|
||||
...userHandler,
|
||||
...brcChanHandlers
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
import {setupServer} from "msw/node";
|
||||
import {allHandlers} from "./index";
|
||||
import {allHandlers} from './index.js';
|
||||
|
||||
export const mockServer = setupServer(...allHandlers)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {};
|
||||
}
|
||||
}
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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 {
|
||||
/**
|
||||
@@ -164,7 +164,6 @@ export class AuthService {
|
||||
});
|
||||
return resp.data.authCode
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
11
src/services/broadcastChannelService.test.ts
Normal file
11
src/services/broadcastChannelService.test.ts
Normal 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")
|
||||
})
|
||||
})
|
||||
94
src/services/broadcastChannelService.ts
Normal file
94
src/services/broadcastChannelService.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
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: newConnId => this.onNewConnId(newConnId),
|
||||
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,
|
||||
disableAutoRemove: true
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("", "")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -1,17 +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";
|
||||
} 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
|
||||
@@ -31,7 +32,7 @@ export class ChatService {
|
||||
})
|
||||
WebSocketHandler.getInstance().registerService({
|
||||
identifier: userid,
|
||||
onNewConnId: this.onNewConnId,
|
||||
onNewConnId: newConnId => this.onNewConnId(newConnId),
|
||||
onNewMessage: wsMessageListener,
|
||||
})
|
||||
}
|
||||
@@ -47,9 +48,9 @@ export class ChatService {
|
||||
async get(): Promise<Chat[]> {
|
||||
try {
|
||||
const resp = await this.client.get<Chat[]>(`chat/get?userid=${this.userid}`);
|
||||
this.database.set("chats", this.userid, JSON.stringify(resp.data))
|
||||
return resp.data
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
@@ -57,6 +58,15 @@ export class ChatService {
|
||||
}
|
||||
}
|
||||
|
||||
async getQuick(): Promise<Chat[]> {
|
||||
const chats = await this.database.get("chats", this.userid)
|
||||
if (chats) {
|
||||
return JSON.parse(chats)
|
||||
} else {
|
||||
throw new Error("No chats in database")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the availability of the specified user
|
||||
* @param userid
|
||||
|
||||
@@ -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", () => {
|
||||
@@ -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)
|
||||
});
|
||||
})
|
||||
@@ -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;
|
||||
@@ -36,14 +36,15 @@ 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()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +54,9 @@ export class DMService {
|
||||
async get(from: number = 0): Promise<Message[]> {
|
||||
try {
|
||||
const resp = await this.client.get<Message[]>(`chat/dm/messages?chatid=${this.chatid}&userid=${this.userid}&from=${from}`);
|
||||
if (from == 0) {
|
||||
this.database.set("messages", this.chatid, JSON.stringify(resp.data))
|
||||
}
|
||||
return resp.data
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
@@ -62,6 +66,15 @@ export class DMService {
|
||||
}
|
||||
}
|
||||
|
||||
async getQuick(): Promise<Message[]> {
|
||||
const messages = await this.database.get("messages", this.chatid)
|
||||
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
|
||||
@@ -117,16 +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): 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)
|
||||
uploadId = await uploader.uploadFiles(tempMsgId, this.chatid, this.userid, attachments, progressListener!)
|
||||
}
|
||||
try {
|
||||
const resp = await this.client.post<Message>("chat/dm/finishMessage", <FinishMessageReq>{
|
||||
@@ -231,10 +246,11 @@ 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,
|
||||
disableAutoRemove: true
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
|
||||
156
src/services/fileTransferService.ts
Normal file
156
src/services/fileTransferService.ts
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
});
|
||||
})
|
||||
@@ -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 {
|
||||
@@ -39,16 +39,18 @@ 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[]): Promise<string> {
|
||||
async uploadFiles(tempMsgId: string, 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 +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])
|
||||
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
|
||||
@@ -92,7 +94,7 @@ export class FileUploadService {
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadFile(uploadId: string, roomId: string, userid: string, file: FileData, registration: FileUploadRegistration): 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);
|
||||
|
||||
@@ -104,6 +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(tempMsgId, file.fileId, totalChunks, i)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {})
|
||||
|
||||
@@ -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,10 +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";
|
||||
} 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;
|
||||
@@ -38,7 +39,7 @@ export class NetworkService {
|
||||
})
|
||||
WebSocketHandler.getInstance().registerService({
|
||||
identifier: networkId,
|
||||
onNewConnId: this.onNewConnId,
|
||||
onNewConnId: newConnId => this.onNewConnId(newConnId),
|
||||
onNewMessage: wsMessageListener,
|
||||
})
|
||||
}
|
||||
@@ -97,6 +98,7 @@ export class NetworkService {
|
||||
const resp = await this.client.post<Network[]>("network/get", <GetNetworksReq>{
|
||||
userid: this.userid,
|
||||
});
|
||||
this.database.set("networks", this.userid, JSON.stringify(resp.data))
|
||||
return resp.data
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
@@ -106,6 +108,15 @@ export class NetworkService {
|
||||
}
|
||||
}
|
||||
|
||||
async getQuick(): Promise<Network[]> {
|
||||
const networks = await this.database.get("networks", this.userid)
|
||||
if (networks) {
|
||||
return JSON.parse(networks)
|
||||
} else {
|
||||
throw new Error("No networks in database")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the invite and joins the network
|
||||
* @param inviteId
|
||||
@@ -700,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) {
|
||||
@@ -852,6 +867,7 @@ export class NetworkService {
|
||||
userid: this.userid,
|
||||
networkId: this.networkId,
|
||||
connId: connId,
|
||||
disableAutoRemove: true
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
@@ -863,7 +879,7 @@ export class NetworkService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches network data from an invite
|
||||
* Fetches network data from an invitation
|
||||
* @param inviteId
|
||||
*/
|
||||
async getFromInvite(inviteId: string): Promise<Network> {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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,8 +10,9 @@ import {
|
||||
DiscoveryResp, EditPictureTitleReq,
|
||||
FinalizeUploadReq,
|
||||
GetResp, PostCommentReq, ToggleFollowReq, TogglePictureLikeReq, UploadImageReq
|
||||
} from "../domain/pictureService.schema";
|
||||
import {environment} from "../core/environment";
|
||||
} from '../domain/pictureService.schema.js';
|
||||
import {environment} from '../core/environment.js';
|
||||
import {Message} from '../domain/dmService.schema.js';
|
||||
|
||||
export class PictureService {
|
||||
userid: string;
|
||||
@@ -42,6 +43,7 @@ export class PictureService {
|
||||
async get(): Promise<GetResp> {
|
||||
try {
|
||||
const resp = await this.client.get<GetResp>(`picture/pictures?userid=${this.userid}&target=${this.uploaderId}`);
|
||||
this.database.set("pictures", this.uploaderId, JSON.stringify(resp.data))
|
||||
return resp.data
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
@@ -52,6 +54,15 @@ export class PictureService {
|
||||
}
|
||||
}
|
||||
|
||||
async getQuick(): Promise<Message[]> {
|
||||
const pictures = await this.database.get("pictures", this.uploaderId)
|
||||
if (pictures) {
|
||||
return JSON.parse(pictures)
|
||||
} else {
|
||||
throw new Error("No pictures in database")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the top 10 most liked and newest pictures
|
||||
*/
|
||||
|
||||
148
src/services/sessionManager.ts
Normal file
148
src/services/sessionManager.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
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) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
if (sessions.length == 0) {
|
||||
throw new Error("No sessions found")
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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);
|
||||
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);
|
||||
activeSessions.push(updatedUserData);
|
||||
}
|
||||
|
||||
return activeSessions;
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
283
src/services/textChannelService.ts
Normal file
283
src/services/textChannelService.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
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: newConnId => this.onNewConnId(newConnId),
|
||||
onNewMessage: wsMessageListener,
|
||||
})
|
||||
}
|
||||
|
||||
private onNewConnId(newConnId: string) {
|
||||
console.log("NetworkService: New connection id")
|
||||
this.client.defaults.headers["X-WS-ID"] = newConnId;
|
||||
this.joinWebSocketRoom().then()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 tempMsgId
|
||||
* @param message
|
||||
* @param replyTo
|
||||
* @param replyToMessage
|
||||
* @param attachments
|
||||
* @param progressListener
|
||||
*/
|
||||
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(tempMsgId, 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.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) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/services/userService.test.ts
Normal file
12
src/services/userService.test.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {describe, expect, it} from "vitest";
|
||||
import {UserService} from './userService.js';
|
||||
import {DatabaseMock} from '../mocks/storage/database.js';
|
||||
|
||||
describe("UserService", () => {
|
||||
const service = new UserService("", "", new DatabaseMock())
|
||||
|
||||
it('should get all sessions', async () => {
|
||||
const sessions = await service.getSessions()
|
||||
expect(sessions[0].token).toBe("sessionToken")
|
||||
});
|
||||
})
|
||||
253
src/services/userService.ts
Normal file
253
src/services/userService.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
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 {DeleteCategoryReq} from '../domain/networkService.schema.js';
|
||||
import {GenericErrorBody} from '../domain/http.schema.js';
|
||||
import {
|
||||
ChangeDisplayNameReq,
|
||||
ChangeEmailReq,
|
||||
ChangePasswordReq, ChangePhoneReq,
|
||||
ChangeUsernameReq, CurrNewCodeTestingResp, DeleteReq, GetSessionsReq, GIF, RegisterFCMTokenReq, Session,
|
||||
ToggleGifSaveReq, UploadNewPfpCdnReq, UploadNewPfpCdnResp,
|
||||
UploadNewPfpReq, VerifyMailChangeReq, VerifyPhoneChange
|
||||
} from '../domain/userService.schema.js';
|
||||
import {RGB} from '../domain/common.schema.js';
|
||||
import {OtpPleCodeSendTestingResp} from '../domain/authService.schema.js';
|
||||
|
||||
export class UserService {
|
||||
userid: string;
|
||||
database: DatabaseAPI;
|
||||
client: AxiosInstance
|
||||
cdnClient: AxiosInstance
|
||||
|
||||
constructor(userid: string, token: string, database: DatabaseAPI) {
|
||||
this.userid = userid;
|
||||
this.database = database;
|
||||
this.client = getClient(false).create({
|
||||
headers: {
|
||||
"Authorization": token,
|
||||
}
|
||||
})
|
||||
this.cdnClient = getClient(true).create({
|
||||
headers: {
|
||||
"Authorization": token,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async changeUsername(username: string): Promise<void> {
|
||||
try {
|
||||
await this.client.patch("user/changeUsername", <ChangeUsernameReq>{
|
||||
userid: this.userid,
|
||||
newUsername: username,
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async changeDisplayName(displayName: string): Promise<void> {
|
||||
try {
|
||||
await this.client.patch("user/changeDisplayName", <ChangeDisplayNameReq>{
|
||||
userid: this.userid,
|
||||
newDisplayName: displayName
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async changePassword(newPassword: string, currentPassword: string): Promise<void> {
|
||||
try {
|
||||
await this.client.patch("user/changePassword", <ChangePasswordReq>{
|
||||
userid: this.userid,
|
||||
currentPassword: currentPassword,
|
||||
newPassword: newPassword,
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async changeEmail(newMail: string, currentPassword: string): Promise<void|CurrNewCodeTestingResp> {
|
||||
try {
|
||||
const resp = await this.client.patch<CurrNewCodeTestingResp>("user/changeEmail", <ChangeEmailReq>{
|
||||
userid: this.userid,
|
||||
currentPassword: currentPassword,
|
||||
newMail: newMail,
|
||||
});
|
||||
if (resp.data.codeCurr != null) {
|
||||
return resp.data
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async uploadNewPfp(pfpId: string): Promise<void> {
|
||||
try {
|
||||
await this.client.patch("user/uploadNewPfp", <UploadNewPfpReq>{
|
||||
userid: this.userid,
|
||||
pfpId: pfpId,
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async uploadNewPfpCdn(image: string | null, monogramLetter: string | null, monogramColors: RGB): Promise<string> {
|
||||
try {
|
||||
const resp = await this.cdnClient.post<UploadNewPfpCdnResp>("pfp", <UploadNewPfpCdnReq>{
|
||||
userid: this.userid,
|
||||
data: image,
|
||||
monogramColors: JSON.stringify(monogramColors),
|
||||
isImage: image !== null,
|
||||
monogramLetter: monogramLetter,
|
||||
});
|
||||
console.log(resp.data.pfpId, "PFPID")
|
||||
return resp.data.pfpId
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async verifyEmailChange(vCodeCurrent: number, vCodeNew: number, newAddress: string): Promise<void> {
|
||||
try {
|
||||
await this.client.patch("user/verifyMailChange", <VerifyMailChangeReq>{
|
||||
userid: this.userid,
|
||||
newAddress: newAddress,
|
||||
vCodeCurrent: vCodeCurrent,
|
||||
vCodeNew: vCodeNew,
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async delete(password: string): Promise<void> {
|
||||
try {
|
||||
await this.client.post("user/deleteAccount", <DeleteReq>{
|
||||
userid: this.userid,
|
||||
password: password
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async registerFirebaseToken(fcmToken: string): Promise<void> {
|
||||
try {
|
||||
await this.client.post("user/registerFcmToken", <RegisterFCMTokenReq>{
|
||||
userid: this.userid,
|
||||
token: fcmToken,
|
||||
});
|
||||
return
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async getSessions(): Promise<Session[]> {
|
||||
try {
|
||||
const resp = await this.client.post<Session[]>("user/getSessions", <GetSessionsReq>{
|
||||
userid: this.userid,
|
||||
});
|
||||
return resp.data
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async toggleGIFSave(url: string): Promise<GIF> {
|
||||
try {
|
||||
const resp = await this.client.patch<GIF>("user/toggleGIFSave", <ToggleGifSaveReq>{
|
||||
userid: this.userid,
|
||||
url: url
|
||||
});
|
||||
return resp.data
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async changePhoneNumber(currentPassword: string, newPhone: string): Promise<CurrNewCodeTestingResp|void> {
|
||||
try {
|
||||
const resp = await this.client.patch<CurrNewCodeTestingResp>("user/changePhone", <ChangePhoneReq>{
|
||||
userid: this.userid,
|
||||
newPhone: newPhone,
|
||||
currentPassword: currentPassword,
|
||||
});
|
||||
if (resp.data.codeCurr != null) {
|
||||
return resp.data
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPhoneNumberChange(newPhone: string, vCodeCurrent: number, vCodeNew: number): Promise<void> {
|
||||
try {
|
||||
const resp = await this.client.patch("user/verifyPhoneChange", <VerifyPhoneChange>{
|
||||
userid: this.userid,
|
||||
newPhone: newPhone,
|
||||
vCodeCurrent: vCodeCurrent,
|
||||
vCodeNew: vCodeNew,
|
||||
});
|
||||
return resp.data
|
||||
} catch (e) {
|
||||
if (isAxiosError<GenericErrorBody>(e)) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error("Unexpected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
6
src/storage/keyvalue.ts
Normal 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;
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
29
tests/broadcastChannelService.test.ts
Normal file
29
tests/broadcastChannelService.test.ts
Normal 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()
|
||||
});
|
||||
})
|
||||
@@ -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"
|
||||
|
||||
@@ -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", () => {
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -17,16 +18,21 @@ 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,
|
||||
[
|
||||
{
|
||||
fileId: uuidv4(),
|
||||
name: "filename",
|
||||
type: "image",
|
||||
extension: "jpeg",
|
||||
data: new File([response.data], "filename", { type: "image/jpeg" })
|
||||
}
|
||||
]
|
||||
],
|
||||
{
|
||||
fileProgressUpdate: () => {},
|
||||
}
|
||||
)
|
||||
});
|
||||
})
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
66
tests/textChannelService.test.ts
Normal file
66
tests/textChannelService.test.ts
Normal 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)
|
||||
});
|
||||
})
|
||||
50
tests/userService.test.ts
Normal file
50
tests/userService.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import {describe, expect, it} from "vitest";
|
||||
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"
|
||||
|
||||
|
||||
describe("UserService Integration Testing", () => {
|
||||
const service = new UserService(USER_SERVICE_TESTING_USER_ID, USER_SERVICE_TESTING_TOKEN, new DatabaseMock())
|
||||
|
||||
it('should not throw on username change', async () => {
|
||||
await service.changeUsername("bob2")
|
||||
});
|
||||
|
||||
it('should not throw on displayName change', async () => {
|
||||
await service.changeDisplayName("New Display Name")
|
||||
});
|
||||
|
||||
it('should set new password', async () => {
|
||||
await service.changePassword("newPasswd", "") // The filler user doesn't have a password set yet
|
||||
});
|
||||
|
||||
it('should set new e-mail', async () => {
|
||||
const code = await service.changeEmail("bob2@example.com", "")
|
||||
expect(code).not.toBeNull()
|
||||
if (code != null) {
|
||||
await service.verifyEmailChange(code.codeCurr??0, code.codeNew??0, "bob2@example.com")
|
||||
}
|
||||
});
|
||||
|
||||
it('should upload a new pfp', async () => {
|
||||
const pfpId = await service.uploadNewPfpCdn(null, "A", <RGB>{r: 255, g: 255, b: 255})
|
||||
await service.uploadNewPfp(pfpId)
|
||||
});
|
||||
|
||||
it('should get sessions', async () => {
|
||||
const sessions = await service.getSessions()
|
||||
expect(sessions[0].token).toBe(USER_SERVICE_TESTING_TOKEN)
|
||||
});
|
||||
|
||||
it('should set new phone', async () => {
|
||||
const code = await service.changePhoneNumber("", "+36201234567")
|
||||
expect(code).not.toBeNull()
|
||||
if (code != null) {
|
||||
await service.verifyPhoneNumberChange("+36201234567", code.codeCurr??0, code.codeNew??0)
|
||||
}
|
||||
});
|
||||
})
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user