diff --git a/package.json b/package.json index caa83d0..1408f2e 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@changesets/cli": "^2.27.7", "@types/jest": "^29.5.12", "@types/node": "^20.14.9", + "@types/ws": "^8.5.11", "jest": "^29.7.0", "prettier": "^3.3.2", "ts-jest": "^29.1.5", @@ -49,6 +50,7 @@ }, "dependencies": { "axios": "^1.7.2", - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "ws": "^8.18.0" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f42f5c5..0accc93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.5 + ws: + specifier: ^8.18.0 + version: 8.18.0 devDependencies: '@changesets/cli': specifier: ^2.27.7 @@ -24,6 +27,9 @@ importers: '@types/node': specifier: ^20.14.9 version: 20.14.9 + '@types/ws': + specifier: ^8.5.11 + version: 8.5.11 jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.14.9)(ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.3)) @@ -681,6 +687,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/ws@8.5.11': + resolution: {integrity: sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2008,6 +2017,18 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2838,6 +2859,10 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/ws@8.5.11': + dependencies: + '@types/node': 20.14.9 + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.32': @@ -4295,6 +4320,8 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 + ws@8.18.0: {} + y18n@5.0.8: {} yallist@2.1.2: {} diff --git a/src/nodb-event-listener.ts b/src/nodb-event-listener.ts new file mode 100644 index 0000000..da9dce7 --- /dev/null +++ b/src/nodb-event-listener.ts @@ -0,0 +1,72 @@ +import WebSocket from "ws"; +import { NodbError } from "./errors"; + +class NodbEventListener { + protected socket: WebSocket | undefined; + + protected connect(props: { + baseUrl: string; + appName: string; + envName?: string; + token: string; + }) { + if (!props.token) { + throw new NodbError("Token is missing!"); + } + const envUrlPart = props.envName ? `/${props.envName}` : ""; + + const formattedBaseUrl = props.baseUrl + .replace("http://", "ws://") + .replace("https://", "wss://"); + this.socket = new WebSocket( + `${formattedBaseUrl}/ws/${props.appName}${envUrlPart}`, + { + headers: { + token: props.token, + }, + }, + ); + + this.socket.on("open", () => { + console.log("Connected to socket"); + }); + + this.socket.onerror = () => { + throw new NodbError(`Something went wrong with socket!`); + }; + + this.listenForMessages(); + } + + protected listenForMessages() { + this.socket?.on("message", (data) => { + const message = data.toString(); + try { + const { + type, + appName, + envName, + data: messageData, + } = JSON.parse(message) as { + type: string; + appName: string; + envName: string; + data: any; + }; + console.log(`Operation ${type.toUpperCase()}`); + console.log( + `Affected environment: ${JSON.stringify({ appName, envName }, null, 2)}`, + ); + console.log(`Data: ${JSON.stringify(messageData, null, 2)}`); + } catch (err) { + console.error("Error parsing JSON:", err); + } + }); + } + + public disconnectFromSocket() { + this.socket?.close(); + } +} + +export default NodbEventListener; diff --git a/src/nodb.ts b/src/nodb.ts index 473806c..dd3286c 100644 --- a/src/nodb.ts +++ b/src/nodb.ts @@ -19,16 +19,19 @@ import { } from "./types"; import { NodbError } from "./errors"; import axios, { Axios, AxiosError } from "axios"; +import NodbEventListener from "./nodb-event-listener"; -class Nodb { +class Nodb extends NodbEventListener { private readonly baseUrl: string; private readonly axios: Axios; - + private token?: string; constructor({ token, baseUrl }: NodbConstructor) { + super(); if (!baseUrl) { throw new NodbError("Missing one of the required dependencies!"); } this.baseUrl = baseUrl; + this.token = token; this.axios = axios.create({ headers: { "Content-Type": "application/json", @@ -41,7 +44,7 @@ class Nodb { (response) => response, (error: AxiosError) => { if (error.response && error.response.status >= 400) { - throw new NodbError(error.response.data as string); + throw new NodbError(JSON.stringify(error.response.data, null, 2)); } return Promise.reject(error); }, @@ -60,6 +63,7 @@ class Nodb { } public setToken(token: string): void { + this.token = token; this.axios.defaults.headers.common.token = token; } @@ -117,14 +121,14 @@ class Nodb { props: BaseAPIProps & PatchRequestBody, ): Promise { const { payload, token, ...urlProps } = props; - const request = await this.axios.put<{ ids: string[] }>( + const response = await this.axios.put<{ ids: string[] }>( this.generateUrl(urlProps), payload, { ...(token && { headers: { token } }), }, ); - return request.data.ids; + return response.data.ids; } async replaceEntity( @@ -212,7 +216,6 @@ class Nodb { environmentDescription, }, ); - return result.data; } @@ -228,7 +231,6 @@ class Nodb { }, { ...(token && { headers: { token } }) }, ); - return result.data; } @@ -242,7 +244,6 @@ class Nodb { `/apps/${appName}/${environmentName}`, { ...(token && { headers: { token } }) }, ); - return result.data.found; } @@ -255,7 +256,6 @@ class Nodb { `/apps/${appName}`, { ...(token && { headers: { token } }) }, ); - return result.data.found; } @@ -272,7 +272,6 @@ class Nodb { }, { ...(token && { headers: { token } }) }, ); - return result.data; } @@ -290,7 +289,6 @@ class Nodb { }, { ...(token && { headers: { token } }) }, ); - return result.data; } @@ -304,7 +302,6 @@ class Nodb { `/tokens/${appName}/${tokenToBeRevoked}`, { ...(token && { headers: { token } }) }, ); - return result.data.success; } @@ -319,9 +316,21 @@ class Nodb { `/tokens/${appName}/${envName}/${tokenToBeRevoked}`, { ...(token && { headers: { token } }) }, ); - return result.data.success; } + + connectToSocket(props: { + appName: string; + envName?: string; + token?: string; + }): void { + this.connect({ + appName: props.appName, + envName: props.envName, + baseUrl: this.baseUrl, + token: props.token || this.token || "", + }); + } } export default Nodb;