From 348b4fd954c402b5d84274fc7abdfc5aa860a707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Fri, 31 May 2024 16:12:20 +0200 Subject: [PATCH 01/14] feat(js): the base js sdk package scaffolding --- packages/js/.eslintrc.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/js/.eslintrc.js diff --git a/packages/js/.eslintrc.js b/packages/js/.eslintrc.js new file mode 100644 index 00000000000..b4d6b44750e --- /dev/null +++ b/packages/js/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + extends: ['../../.eslintrc.js'], + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'error', + '@typescript-eslint/naming-convention': [ + 'error', + { + filter: '_', + selector: 'variableLike', + leadingUnderscore: 'allow', + format: ['PascalCase', 'camelCase', 'UPPER_CASE'], + }, + ], + }, + parserOptions: { + project: './tsconfig.json', + ecmaVersion: 2020, + sourceType: 'module', + }, +}; From 9a85fb5f0a19728ebc6fcf16a7a754d0fb04d591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Sun, 2 Jun 2024 22:33:15 +0200 Subject: [PATCH 02/14] feat(js): improve the package json exports and tsup config --- packages/js/.eslintrc.js | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 packages/js/.eslintrc.js diff --git a/packages/js/.eslintrc.js b/packages/js/.eslintrc.js deleted file mode 100644 index b4d6b44750e..00000000000 --- a/packages/js/.eslintrc.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], - rules: { - '@typescript-eslint/explicit-module-boundary-types': 'error', - '@typescript-eslint/naming-convention': [ - 'error', - { - filter: '_', - selector: 'variableLike', - leadingUnderscore: 'allow', - format: ['PascalCase', 'camelCase', 'UPPER_CASE'], - }, - ], - }, - parserOptions: { - project: './tsconfig.json', - ecmaVersion: 2020, - sourceType: 'module', - }, -}; From a2627c6aa8491747dedb840628fe8d26c3f2c786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Mon, 3 Jun 2024 16:12:58 +0200 Subject: [PATCH 03/14] feat(js): lazy session initialization and interface fixes --- packages/js/src/novu.spec.ts | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 packages/js/src/novu.spec.ts diff --git a/packages/js/src/novu.spec.ts b/packages/js/src/novu.spec.ts new file mode 100644 index 00000000000..2feae35c80a --- /dev/null +++ b/packages/js/src/novu.spec.ts @@ -0,0 +1,83 @@ +import { Novu } from './novu'; + +const mockFeedResponse = { + data: [], + hasMore: true, + totalCount: 0, + pageSize: 10, + page: 1, +}; + +const initializeSession = jest.fn().mockResolvedValue({ token: 'token', profile: 'profile' }); +const getNotificationsList = jest.fn(() => mockFeedResponse); + +jest.mock('@novu/client', () => ({ + ...jest.requireActual('@novu/client'), + ApiService: jest.fn().mockImplementation(() => { + const apiService = { + isAuthenticated: false, + initializeSession, + setAuthorizationToken: jest.fn(() => { + apiService.isAuthenticated = true; + }), + getNotificationsList, + }; + + return apiService; + }), +})); + +describe('Novu', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('lazy session initialization', () => { + test('should call the queued feeds.fetch after the session is initialized', async () => { + const page = 1; + const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); + const res = await novu.feeds.fetch({ page }); + + expect(initializeSession).toHaveBeenCalledTimes(1); + expect(getNotificationsList).toHaveBeenCalledWith(page, {}); + expect(res).toEqual(mockFeedResponse); + }); + + test('should call the feeds.fetch right away when session is already initialized', async () => { + const page = 1; + const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); + // await for session initialization + await new Promise((resolve) => setTimeout(resolve, 10)); + + const res = await novu.feeds.fetch({ page }); + + expect(initializeSession).toHaveBeenCalledTimes(1); + expect(getNotificationsList).toHaveBeenCalledWith(page, {}); + expect(res).toEqual(mockFeedResponse); + }); + + test('should reject the queued feeds.fetch if session initialization fails', async () => { + const page = 1; + const error = new Error('Failed to initialize session'); + initializeSession.mockRejectedValueOnce(error); + const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); + + const fetchPromise = novu.feeds.fetch({ page }); + + await expect(fetchPromise).rejects.toEqual(error); + }); + + test('should reject the feeds.fetch right away when session initialization has failed', async () => { + const page = 1; + const error = new Error('Failed to initialize session'); + initializeSession.mockRejectedValueOnce(error); + const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); + // await for session initialization + await new Promise((resolve) => setTimeout(resolve, 10)); + + const fetchPromise = novu.feeds.fetch({ page }); + + await expect(fetchPromise).rejects.toEqual(error); + }); + }); +}); From c753c571a8989eb8951e938c9b13925f946449bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Fri, 7 Jun 2024 09:52:13 +0200 Subject: [PATCH 04/14] feat(js): renamed the *.spec.ts to *.test.ts --- packages/js/src/novu.spec.ts | 83 ------------------------------------ 1 file changed, 83 deletions(-) delete mode 100644 packages/js/src/novu.spec.ts diff --git a/packages/js/src/novu.spec.ts b/packages/js/src/novu.spec.ts deleted file mode 100644 index 2feae35c80a..00000000000 --- a/packages/js/src/novu.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Novu } from './novu'; - -const mockFeedResponse = { - data: [], - hasMore: true, - totalCount: 0, - pageSize: 10, - page: 1, -}; - -const initializeSession = jest.fn().mockResolvedValue({ token: 'token', profile: 'profile' }); -const getNotificationsList = jest.fn(() => mockFeedResponse); - -jest.mock('@novu/client', () => ({ - ...jest.requireActual('@novu/client'), - ApiService: jest.fn().mockImplementation(() => { - const apiService = { - isAuthenticated: false, - initializeSession, - setAuthorizationToken: jest.fn(() => { - apiService.isAuthenticated = true; - }), - getNotificationsList, - }; - - return apiService; - }), -})); - -describe('Novu', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('lazy session initialization', () => { - test('should call the queued feeds.fetch after the session is initialized', async () => { - const page = 1; - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - const res = await novu.feeds.fetch({ page }); - - expect(initializeSession).toHaveBeenCalledTimes(1); - expect(getNotificationsList).toHaveBeenCalledWith(page, {}); - expect(res).toEqual(mockFeedResponse); - }); - - test('should call the feeds.fetch right away when session is already initialized', async () => { - const page = 1; - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - // await for session initialization - await new Promise((resolve) => setTimeout(resolve, 10)); - - const res = await novu.feeds.fetch({ page }); - - expect(initializeSession).toHaveBeenCalledTimes(1); - expect(getNotificationsList).toHaveBeenCalledWith(page, {}); - expect(res).toEqual(mockFeedResponse); - }); - - test('should reject the queued feeds.fetch if session initialization fails', async () => { - const page = 1; - const error = new Error('Failed to initialize session'); - initializeSession.mockRejectedValueOnce(error); - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - - const fetchPromise = novu.feeds.fetch({ page }); - - await expect(fetchPromise).rejects.toEqual(error); - }); - - test('should reject the feeds.fetch right away when session initialization has failed', async () => { - const page = 1; - const error = new Error('Failed to initialize session'); - initializeSession.mockRejectedValueOnce(error); - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - // await for session initialization - await new Promise((resolve) => setTimeout(resolve, 10)); - - const fetchPromise = novu.feeds.fetch({ page }); - - await expect(fetchPromise).rejects.toEqual(error); - }); - }); -}); From 438661f167371e63bc7146ec04fba7e8fa6f381a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Thu, 6 Jun 2024 17:22:10 +0200 Subject: [PATCH 05/14] feat(js): js sdk feeds module --- .../js/src/utils/api-service-signleton.ts | 20 ++ pnpm-lock.yaml | 239 +----------------- 2 files changed, 34 insertions(+), 225 deletions(-) create mode 100644 packages/js/src/utils/api-service-signleton.ts diff --git a/packages/js/src/utils/api-service-signleton.ts b/packages/js/src/utils/api-service-signleton.ts new file mode 100644 index 00000000000..c4696ef2d0c --- /dev/null +++ b/packages/js/src/utils/api-service-signleton.ts @@ -0,0 +1,20 @@ +import { ApiService } from '@novu/client'; + +const PRODUCTION_BACKEND_URL = 'https://api.novu.co'; + +type ApiServiceOptions = { + backendUrl?: string; +}; + +export class ApiServiceSingleton { + static #instance: ApiService; + + static getInstance(options?: ApiServiceOptions): ApiService { + const isNeedsToRecreate = !!options; + if (isNeedsToRecreate) { + ApiServiceSingleton.#instance = new ApiService(options.backendUrl ?? PRODUCTION_BACKEND_URL); + } + + return ApiServiceSingleton.#instance; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 408c13395d3..42bb5ce425b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3488,7 +3488,7 @@ importers: version: 13.5.6(@babel/core@7.24.4)(@opentelemetry/api@1.7.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.64.1) prettier: specifier: ^3.2.5 - version: 3.3.2 + version: 3.2.5 ts-node: specifier: ^10.9.2 version: 10.9.2(@swc/core@1.3.107)(@types/node@20.14.2)(typescript@5.4.5) @@ -31839,8 +31839,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -31995,7 +31995,7 @@ snapshots: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sso-oidc': 3.575.0 - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-bucket-endpoint': 3.575.0 @@ -32225,7 +32225,7 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -32266,52 +32266,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.575.0 - '@aws-sdk/core': 3.575.0 - '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/middleware-host-header': 3.575.0 - '@aws-sdk/middleware-logger': 3.575.0 - '@aws-sdk/middleware-recursion-detection': 3.575.0 - '@aws-sdk/middleware-user-agent': 3.575.0 - '@aws-sdk/region-config-resolver': 3.575.0 - '@aws-sdk/types': 3.575.0 - '@aws-sdk/util-endpoints': 3.575.0 - '@aws-sdk/util-user-agent-browser': 3.575.0 - '@aws-sdk/util-user-agent-node': 3.575.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.0 - '@smithy/fetch-http-handler': 3.0.0 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.0 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.0 - '@smithy/util-defaults-mode-node': 3.0.0 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sts' - - aws-crt - '@aws-sdk/client-sso@3.382.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 @@ -32604,13 +32558,13 @@ snapshots: - aws-crt optional: true - '@aws-sdk/client-sts@3.575.0': + '@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sso-oidc': 3.575.0 '@aws-sdk/core': 3.575.0 - '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 '@aws-sdk/middleware-logger': 3.575.0 '@aws-sdk/middleware-recursion-detection': 3.575.0 @@ -32647,6 +32601,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/core@3.496.0': @@ -32782,26 +32737,9 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0)': - dependencies: - '@aws-sdk/client-sts': 3.575.0 - '@aws-sdk/credential-provider-env': 3.575.0 - '@aws-sdk/credential-provider-process': 3.575.0 - '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)) - '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/types': 3.575.0 - '@smithy/credential-provider-imds': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/credential-provider-env': 3.575.0 '@aws-sdk/credential-provider-process': 3.575.0 '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) @@ -32866,25 +32804,6 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-node@3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0)': - dependencies: - '@aws-sdk/credential-provider-env': 3.575.0 - '@aws-sdk/credential-provider-http': 3.575.0 - '@aws-sdk/credential-provider-ini': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/credential-provider-process': 3.575.0 - '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)) - '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/types': 3.575.0 - '@smithy/credential-provider-imds': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - '@aws-sdk/client-sts' - - aws-crt - '@aws-sdk/credential-provider-node@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0)': dependencies: '@aws-sdk/credential-provider-env': 3.575.0 @@ -32975,19 +32894,6 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-sso@3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))': - dependencies: - '@aws-sdk/client-sso': 3.575.0 - '@aws-sdk/token-providers': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)) - '@aws-sdk/types': 3.575.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - '@aws-sdk/credential-provider-sso@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: '@aws-sdk/client-sso': 3.575.0 @@ -33029,7 +32935,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/types': 3.575.0 '@smithy/property-provider': 3.0.0 '@smithy/types': 3.0.0 @@ -33451,15 +33357,6 @@ snapshots: - aws-crt optional: true - '@aws-sdk/token-providers@3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))': - dependencies: - '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/types': 3.575.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - '@aws-sdk/token-providers@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: '@aws-sdk/client-sso-oidc': 3.575.0 @@ -39290,43 +39187,6 @@ snapshots: - ts-node - utf-8-validate - '@jest/core@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5))': - dependencies: - '@jest/console': 27.5.1 - '@jest/reporters': 27.5.1 - '@jest/test-result': 27.5.1 - '@jest/transform': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.42 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.8.1 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 27.5.1 - jest-config: 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) - jest-haste-map: 27.5.1 - jest-message-util: 27.5.1 - jest-regex-util: 27.5.1 - jest-resolve: 27.5.1 - jest-resolve-dependencies: 27.5.1 - jest-runner: 27.5.1 - jest-runtime: 27.5.1 - jest-snapshot: 27.5.1 - jest-util: 27.5.1 - jest-validate: 27.5.1 - jest-watcher: 27.5.1 - micromatch: 4.0.5 - rimraf: 3.0.2 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - ts-node - - utf-8-validate - '@jest/core@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.14.2)(typescript@4.9.5))': dependencies: '@jest/console': 27.5.1 @@ -60246,14 +60106,14 @@ snapshots: jest-cli@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): dependencies: - '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) + '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5)) '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) + jest-config: 27.5.1(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5)) jest-util: 27.5.1 jest-validate: 27.5.1 prompts: 2.4.2 @@ -60617,40 +60477,6 @@ snapshots: - supports-color - utf-8-validate - jest-config@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): - dependencies: - '@babel/core': 7.24.4 - '@jest/test-sequencer': 27.5.1 - '@jest/types': 27.5.1 - babel-jest: 27.5.1(@babel/core@7.24.4) - chalk: 4.1.2 - ci-info: 3.8.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 27.5.1 - jest-environment-jsdom: 27.5.1 - jest-environment-node: 27.5.1 - jest-get-type: 27.5.1 - jest-jasmine2: 27.5.1 - jest-regex-util: 27.5.1 - jest-resolve: 27.5.1 - jest-runner: 27.5.1 - jest-util: 27.5.1 - jest-validate: 27.5.1 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 27.5.1 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - ts-node: 10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5) - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - utf-8-validate - jest-config@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.14.2)(typescript@4.9.5)): dependencies: '@babel/core': 7.24.4 @@ -61832,7 +61658,7 @@ snapshots: jest@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): dependencies: - '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) + '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5)) import-local: 3.1.0 jest-cli: 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) transitivePeerDependencies: @@ -66785,14 +66611,6 @@ snapshots: postcss: 8.4.31 ts-node: 10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5) - postcss-load-config@3.1.4(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): - dependencies: - lilconfig: 2.1.0 - yaml: 1.10.2 - optionalDependencies: - postcss: 8.4.31 - ts-node: 10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5) - postcss-load-config@3.1.4(postcss@8.4.38)(ts-node@10.9.1(@swc/core@1.3.107)(@types/node@16.11.7)(typescript@4.9.5)): dependencies: lilconfig: 2.1.0 @@ -68929,7 +68747,7 @@ snapshots: semver: 7.5.4 source-map-loader: 3.0.2(webpack@5.78.0(@swc/core@1.3.107)) style-loader: 3.3.2(webpack@5.78.0(@swc/core@1.3.107)) - tailwindcss: 3.3.1(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) + tailwindcss: 3.3.1(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5)) terser-webpack-plugin: 5.3.7(@swc/core@1.3.107)(webpack@5.78.0(@swc/core@1.3.107)) webpack: 5.78.0(@swc/core@1.3.107) webpack-dev-server: 4.11.1(webpack@5.78.0(@swc/core@1.3.107)) @@ -71395,35 +71213,6 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@3.3.1(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): - dependencies: - arg: 5.0.2 - chokidar: 3.6.0 - color-name: 1.1.4 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.1 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.0 - lilconfig: 2.1.0 - micromatch: 4.0.5 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.0 - postcss: 8.4.31 - postcss-import: 14.1.0(postcss@8.4.31) - postcss-js: 4.0.1(postcss@8.4.31) - postcss-load-config: 3.1.4(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) - postcss-nested: 6.0.0(postcss@8.4.31) - postcss-selector-parser: 6.0.13 - postcss-value-parser: 4.2.0 - quick-lru: 5.1.1 - resolve: 1.22.8 - sucrase: 3.32.0 - transitivePeerDependencies: - - ts-node - tailwindcss@3.3.1(postcss@8.4.38)(ts-node@10.9.1(@swc/core@1.3.107)(@types/node@16.11.7)(typescript@4.9.5)): dependencies: arg: 5.0.2 From dc661863a6983e3aa4965443945044247fb1da06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Tue, 11 Jun 2024 11:00:22 +0200 Subject: [PATCH 06/14] chore(js): spell check fix --- .../js/src/utils/api-service-signleton.ts | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 packages/js/src/utils/api-service-signleton.ts diff --git a/packages/js/src/utils/api-service-signleton.ts b/packages/js/src/utils/api-service-signleton.ts deleted file mode 100644 index c4696ef2d0c..00000000000 --- a/packages/js/src/utils/api-service-signleton.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ApiService } from '@novu/client'; - -const PRODUCTION_BACKEND_URL = 'https://api.novu.co'; - -type ApiServiceOptions = { - backendUrl?: string; -}; - -export class ApiServiceSingleton { - static #instance: ApiService; - - static getInstance(options?: ApiServiceOptions): ApiService { - const isNeedsToRecreate = !!options; - if (isNeedsToRecreate) { - ApiServiceSingleton.#instance = new ApiService(options.backendUrl ?? PRODUCTION_BACKEND_URL); - } - - return ApiServiceSingleton.#instance; - } -} From d035e6f44024ac92760f6eec909df817c518c82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Fri, 31 May 2024 16:12:20 +0200 Subject: [PATCH 07/14] feat(js): the base js sdk package scaffolding --- packages/js/.eslintrc.js | 20 ++++++++++++++++++++ pnpm-lock.yaml | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 packages/js/.eslintrc.js diff --git a/packages/js/.eslintrc.js b/packages/js/.eslintrc.js new file mode 100644 index 00000000000..b4d6b44750e --- /dev/null +++ b/packages/js/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + extends: ['../../.eslintrc.js'], + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'error', + '@typescript-eslint/naming-convention': [ + 'error', + { + filter: '_', + selector: 'variableLike', + leadingUnderscore: 'allow', + format: ['PascalCase', 'camelCase', 'UPPER_CASE'], + }, + ], + }, + parserOptions: { + project: './tsconfig.json', + ecmaVersion: 2020, + sourceType: 'module', + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42bb5ce425b..004bd970d59 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71384,7 +71384,7 @@ snapshots: terser-webpack-plugin@5.3.9(@swc/core@1.3.107(@swc/helpers@0.5.2))(esbuild@0.18.20)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.2))(esbuild@0.18.20)): dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.1.2 serialize-javascript: 6.0.1 @@ -71418,7 +71418,7 @@ snapshots: terser-webpack-plugin@5.3.9(@swc/core@1.3.107)(esbuild@0.18.17)(webpack@5.88.2(@swc/core@1.3.107)(esbuild@0.18.17)): dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.1.2 serialize-javascript: 6.0.1 @@ -71441,7 +71441,7 @@ snapshots: terser-webpack-plugin@5.3.9(@swc/core@1.3.107)(webpack@5.78.0(@swc/core@1.3.107)): dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.1.2 serialize-javascript: 6.0.1 From 9d75acc42bebc9ff1456d3dc6e55bd0cec279c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Mon, 3 Jun 2024 15:10:12 +0200 Subject: [PATCH 08/14] feat(js): set the dist file size limits and run the check after the build --- pnpm-lock.yaml | 221 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 209 insertions(+), 12 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 004bd970d59..a7c268cf647 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31995,7 +31995,7 @@ snapshots: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sso-oidc': 3.575.0 - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-bucket-endpoint': 3.575.0 @@ -32225,7 +32225,7 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -32558,7 +32558,7 @@ snapshots: - aws-crt optional: true - '@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': + '@aws-sdk/client-sts@3.575.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -32600,6 +32600,51 @@ snapshots: '@smithy/util-retry': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sso-oidc': 3.575.0 + '@aws-sdk/core': 3.575.0 + '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)) + '@aws-sdk/middleware-host-header': 3.575.0 + '@aws-sdk/middleware-logger': 3.575.0 + '@aws-sdk/middleware-recursion-detection': 3.575.0 + '@aws-sdk/middleware-user-agent': 3.575.0 + '@aws-sdk/region-config-resolver': 3.575.0 + '@aws-sdk/types': 3.575.0 + '@aws-sdk/util-endpoints': 3.575.0 + '@aws-sdk/util-user-agent-browser': 3.575.0 + '@aws-sdk/util-user-agent-node': 3.575.0 + '@smithy/config-resolver': 3.0.0 + '@smithy/core': 2.0.0 + '@smithy/fetch-http-handler': 3.0.0 + '@smithy/hash-node': 3.0.0 + '@smithy/invalid-dependency': 3.0.0 + '@smithy/middleware-content-length': 3.0.0 + '@smithy/middleware-endpoint': 3.0.0 + '@smithy/middleware-retry': 3.0.0 + '@smithy/middleware-serde': 3.0.0 + '@smithy/middleware-stack': 3.0.0 + '@smithy/node-config-provider': 3.0.0 + '@smithy/node-http-handler': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.0.0 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.0 + '@smithy/util-defaults-mode-node': 3.0.0 + '@smithy/util-endpoints': 2.0.0 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-retry': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -32737,12 +32782,29 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0)': + '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0))': dependencies: '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/credential-provider-env': 3.575.0 '@aws-sdk/credential-provider-process': 3.575.0 '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)) + '@aws-sdk/types': 3.575.0 + '@smithy/credential-provider-imds': 3.0.0 + '@smithy/property-provider': 3.0.0 + '@smithy/shared-ini-file-loader': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0)': + dependencies: + '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/credential-provider-env': 3.575.0 + '@aws-sdk/credential-provider-process': 3.575.0 + '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0) '@aws-sdk/types': 3.575.0 '@smithy/credential-provider-imds': 3.0.0 @@ -32804,6 +32866,25 @@ snapshots: - aws-crt optional: true + '@aws-sdk/credential-provider-node@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0))': + dependencies: + '@aws-sdk/credential-provider-env': 3.575.0 + '@aws-sdk/credential-provider-http': 3.575.0 + '@aws-sdk/credential-provider-ini': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)) + '@aws-sdk/credential-provider-process': 3.575.0 + '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)) + '@aws-sdk/types': 3.575.0 + '@smithy/credential-provider-imds': 3.0.0 + '@smithy/property-provider': 3.0.0 + '@smithy/shared-ini-file-loader': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + '@aws-sdk/credential-provider-node@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0)': dependencies: '@aws-sdk/credential-provider-env': 3.575.0 @@ -32933,7 +33014,7 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0)': + '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0))': dependencies: '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) '@aws-sdk/types': 3.575.0 @@ -32941,6 +33022,14 @@ snapshots: '@smithy/types': 3.0.0 tslib: 2.6.2 + '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0)': + dependencies: + '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/types': 3.575.0 + '@smithy/property-provider': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + '@aws-sdk/credential-providers@3.504.1': dependencies: '@aws-sdk/client-cognito-identity': 3.504.0 @@ -39187,6 +39276,43 @@ snapshots: - ts-node - utf-8-validate + '@jest/core@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5))': + dependencies: + '@jest/console': 27.5.1 + '@jest/reporters': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 14.18.42 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.8.1 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 27.5.1 + jest-config: 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) + jest-haste-map: 27.5.1 + jest-message-util: 27.5.1 + jest-regex-util: 27.5.1 + jest-resolve: 27.5.1 + jest-resolve-dependencies: 27.5.1 + jest-runner: 27.5.1 + jest-runtime: 27.5.1 + jest-snapshot: 27.5.1 + jest-util: 27.5.1 + jest-validate: 27.5.1 + jest-watcher: 27.5.1 + micromatch: 4.0.5 + rimraf: 3.0.2 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + '@jest/core@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.14.2)(typescript@4.9.5))': dependencies: '@jest/console': 27.5.1 @@ -60106,14 +60232,14 @@ snapshots: jest-cli@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): dependencies: - '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5)) + '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 27.5.1(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5)) + jest-config: 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) jest-util: 27.5.1 jest-validate: 27.5.1 prompts: 2.4.2 @@ -60477,6 +60603,40 @@ snapshots: - supports-color - utf-8-validate + jest-config@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): + dependencies: + '@babel/core': 7.24.4 + '@jest/test-sequencer': 27.5.1 + '@jest/types': 27.5.1 + babel-jest: 27.5.1(@babel/core@7.24.4) + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 27.5.1 + jest-environment-jsdom: 27.5.1 + jest-environment-node: 27.5.1 + jest-get-type: 27.5.1 + jest-jasmine2: 27.5.1 + jest-regex-util: 27.5.1 + jest-resolve: 27.5.1 + jest-runner: 27.5.1 + jest-util: 27.5.1 + jest-validate: 27.5.1 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 27.5.1 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + ts-node: 10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5) + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + jest-config@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@20.14.2)(typescript@4.9.5)): dependencies: '@babel/core': 7.24.4 @@ -61658,7 +61818,7 @@ snapshots: jest@27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): dependencies: - '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5)) + '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) import-local: 3.1.0 jest-cli: 27.5.1(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) transitivePeerDependencies: @@ -66611,6 +66771,14 @@ snapshots: postcss: 8.4.31 ts-node: 10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5) + postcss-load-config@3.1.4(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.4.31 + ts-node: 10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5) + postcss-load-config@3.1.4(postcss@8.4.38)(ts-node@10.9.1(@swc/core@1.3.107)(@types/node@16.11.7)(typescript@4.9.5)): dependencies: lilconfig: 2.1.0 @@ -68747,7 +68915,7 @@ snapshots: semver: 7.5.4 source-map-loader: 3.0.2(webpack@5.78.0(@swc/core@1.3.107)) style-loader: 3.3.2(webpack@5.78.0(@swc/core@1.3.107)) - tailwindcss: 3.3.1(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107(@swc/helpers@0.5.2))(@types/node@12.20.55)(typescript@4.9.5)) + tailwindcss: 3.3.1(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) terser-webpack-plugin: 5.3.7(@swc/core@1.3.107)(webpack@5.78.0(@swc/core@1.3.107)) webpack: 5.78.0(@swc/core@1.3.107) webpack-dev-server: 4.11.1(webpack@5.78.0(@swc/core@1.3.107)) @@ -71213,6 +71381,35 @@ snapshots: transitivePeerDependencies: - ts-node + tailwindcss@3.3.1(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)): + dependencies: + arg: 5.0.2 + chokidar: 3.6.0 + color-name: 1.1.4 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.1 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.0 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.31 + postcss-import: 14.1.0(postcss@8.4.31) + postcss-js: 4.0.1(postcss@8.4.31) + postcss-load-config: 3.1.4(postcss@8.4.31)(ts-node@10.9.2(@swc/core@1.3.107)(@types/node@12.20.55)(typescript@4.9.5)) + postcss-nested: 6.0.0(postcss@8.4.31) + postcss-selector-parser: 6.0.13 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + resolve: 1.22.8 + sucrase: 3.32.0 + transitivePeerDependencies: + - ts-node + tailwindcss@3.3.1(postcss@8.4.38)(ts-node@10.9.1(@swc/core@1.3.107)(@types/node@16.11.7)(typescript@4.9.5)): dependencies: arg: 5.0.2 @@ -71384,7 +71581,7 @@ snapshots: terser-webpack-plugin@5.3.9(@swc/core@1.3.107(@swc/helpers@0.5.2))(esbuild@0.18.20)(webpack@5.78.0(@swc/core@1.3.107(@swc/helpers@0.5.2))(esbuild@0.18.20)): dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.18 jest-worker: 27.5.1 schema-utils: 3.1.2 serialize-javascript: 6.0.1 @@ -71418,7 +71615,7 @@ snapshots: terser-webpack-plugin@5.3.9(@swc/core@1.3.107)(esbuild@0.18.17)(webpack@5.88.2(@swc/core@1.3.107)(esbuild@0.18.17)): dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.18 jest-worker: 27.5.1 schema-utils: 3.1.2 serialize-javascript: 6.0.1 @@ -71441,7 +71638,7 @@ snapshots: terser-webpack-plugin@5.3.9(@swc/core@1.3.107)(webpack@5.78.0(@swc/core@1.3.107)): dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.18 jest-worker: 27.5.1 schema-utils: 3.1.2 serialize-javascript: 6.0.1 From b19f98f7cf082e81dedc157ff5de9de04d777d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Mon, 3 Jun 2024 16:12:58 +0200 Subject: [PATCH 09/14] feat(js): lazy session initialization and interface fixes --- packages/js/src/feeds/notification.ts | 3 + packages/js/src/novu.spec.ts | 83 +++++++++++++++++++++++++++ packages/js/src/novu.ts | 7 ++- packages/js/src/utils/base-module.ts | 48 ++++++++++++++++ 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 packages/js/src/novu.spec.ts create mode 100644 packages/js/src/utils/base-module.ts diff --git a/packages/js/src/feeds/notification.ts b/packages/js/src/feeds/notification.ts index 66d437a0b12..79b5135f35b 100644 --- a/packages/js/src/feeds/notification.ts +++ b/packages/js/src/feeds/notification.ts @@ -22,6 +22,7 @@ type NotificationLike = Pick< | 'actor' | 'subscriber' | 'transactionId' + | 'templateIdentifier' | 'content' | 'read' | 'seen' @@ -42,6 +43,7 @@ export class Notification implements Pick { actor?: Actor; subscriber?: Subscriber; transactionId: string; + templateIdentifier: string; content: string; read: boolean; seen: boolean; @@ -61,6 +63,7 @@ export class Notification implements Pick { this.actor = notification.actor; this.subscriber = notification.subscriber; this.transactionId = notification.transactionId; + this.templateIdentifier = notification.templateIdentifier; this.content = notification.content; this.read = notification.read; this.seen = notification.seen; diff --git a/packages/js/src/novu.spec.ts b/packages/js/src/novu.spec.ts new file mode 100644 index 00000000000..2feae35c80a --- /dev/null +++ b/packages/js/src/novu.spec.ts @@ -0,0 +1,83 @@ +import { Novu } from './novu'; + +const mockFeedResponse = { + data: [], + hasMore: true, + totalCount: 0, + pageSize: 10, + page: 1, +}; + +const initializeSession = jest.fn().mockResolvedValue({ token: 'token', profile: 'profile' }); +const getNotificationsList = jest.fn(() => mockFeedResponse); + +jest.mock('@novu/client', () => ({ + ...jest.requireActual('@novu/client'), + ApiService: jest.fn().mockImplementation(() => { + const apiService = { + isAuthenticated: false, + initializeSession, + setAuthorizationToken: jest.fn(() => { + apiService.isAuthenticated = true; + }), + getNotificationsList, + }; + + return apiService; + }), +})); + +describe('Novu', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('lazy session initialization', () => { + test('should call the queued feeds.fetch after the session is initialized', async () => { + const page = 1; + const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); + const res = await novu.feeds.fetch({ page }); + + expect(initializeSession).toHaveBeenCalledTimes(1); + expect(getNotificationsList).toHaveBeenCalledWith(page, {}); + expect(res).toEqual(mockFeedResponse); + }); + + test('should call the feeds.fetch right away when session is already initialized', async () => { + const page = 1; + const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); + // await for session initialization + await new Promise((resolve) => setTimeout(resolve, 10)); + + const res = await novu.feeds.fetch({ page }); + + expect(initializeSession).toHaveBeenCalledTimes(1); + expect(getNotificationsList).toHaveBeenCalledWith(page, {}); + expect(res).toEqual(mockFeedResponse); + }); + + test('should reject the queued feeds.fetch if session initialization fails', async () => { + const page = 1; + const error = new Error('Failed to initialize session'); + initializeSession.mockRejectedValueOnce(error); + const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); + + const fetchPromise = novu.feeds.fetch({ page }); + + await expect(fetchPromise).rejects.toEqual(error); + }); + + test('should reject the feeds.fetch right away when session initialization has failed', async () => { + const page = 1; + const error = new Error('Failed to initialize session'); + initializeSession.mockRejectedValueOnce(error); + const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); + // await for session initialization + await new Promise((resolve) => setTimeout(resolve, 10)); + + const fetchPromise = novu.feeds.fetch({ page }); + + await expect(fetchPromise).rejects.toEqual(error); + }); + }); +}); diff --git a/packages/js/src/novu.ts b/packages/js/src/novu.ts index 22e3abff9f4..313129298cc 100644 --- a/packages/js/src/novu.ts +++ b/packages/js/src/novu.ts @@ -1,3 +1,5 @@ +import { ApiService } from '@novu/client'; + import { NovuEventEmitter } from './event-emitter'; import type { EventHandler, EventNames, Events } from './event-emitter'; import { Feeds } from './feeds'; @@ -5,6 +7,8 @@ import { Session } from './session'; import { Preferences } from './preferences'; import { ApiServiceSingleton } from './utils/api-service-singleton'; +const PRODUCTION_BACKEND_URL = 'https://api.novu.co'; + type NovuOptions = { applicationIdentifier: string; subscriberId: string; @@ -15,12 +19,13 @@ type NovuOptions = { export class Novu implements Pick { #emitter: NovuEventEmitter; #session: Session; + #apiService: ApiService; public readonly feeds: Feeds; public readonly preferences: Preferences; constructor(options: NovuOptions) { - ApiServiceSingleton.getInstance({ backendUrl: options.backendUrl }); + ApiServiceSingleton.getInstance({ backendUrl: options.backendUrl ?? PRODUCTION_BACKEND_URL }); this.#emitter = NovuEventEmitter.getInstance({ recreate: true }); this.#session = new Session({ applicationIdentifier: options.applicationIdentifier, diff --git a/packages/js/src/utils/base-module.ts b/packages/js/src/utils/base-module.ts new file mode 100644 index 00000000000..fe170266a46 --- /dev/null +++ b/packages/js/src/utils/base-module.ts @@ -0,0 +1,48 @@ +import { ApiService } from 'client/dist/cjs'; +import { NovuEventEmitter } from '../event-emitter'; + +interface CallQueueItem { + fn: () => Promise; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + resolve: (value: any | PromiseLike) => void; + reject: (reason?: unknown) => void; +} + +export class BaseModule { + _apiService: ApiService; + _emitter: NovuEventEmitter; + #callsQueue: CallQueueItem[] = []; + #sessionError: unknown; + + constructor(emitter: NovuEventEmitter, apiService: ApiService) { + this._emitter = emitter; + this._apiService = apiService; + this._emitter.on('session.initialize.success', () => { + this.#callsQueue.forEach(async ({ fn, resolve }) => { + resolve(await fn()); + }); + this.#callsQueue = []; + }); + this._emitter.on('session.initialize.error', ({ error }) => { + this.#sessionError = error; + this.#callsQueue.forEach(({ reject }) => { + reject(error); + }); + this.#callsQueue = []; + }); + } + + async callWithSession(fn: () => Promise): Promise { + if (this._apiService.isAuthenticated) { + return fn(); + } + + if (this.#sessionError) { + return Promise.reject(this.#sessionError); + } + + return new Promise(async (resolve, reject) => { + this.#callsQueue.push({ fn, resolve, reject }); + }); + } +} From cc21e684bb8a35979f1c2a998f8e2aaa538248b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Fri, 7 Jun 2024 09:52:13 +0200 Subject: [PATCH 10/14] feat(js): renamed the *.spec.ts to *.test.ts --- packages/js/src/novu.spec.ts | 83 ------------------------------------ 1 file changed, 83 deletions(-) delete mode 100644 packages/js/src/novu.spec.ts diff --git a/packages/js/src/novu.spec.ts b/packages/js/src/novu.spec.ts deleted file mode 100644 index 2feae35c80a..00000000000 --- a/packages/js/src/novu.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Novu } from './novu'; - -const mockFeedResponse = { - data: [], - hasMore: true, - totalCount: 0, - pageSize: 10, - page: 1, -}; - -const initializeSession = jest.fn().mockResolvedValue({ token: 'token', profile: 'profile' }); -const getNotificationsList = jest.fn(() => mockFeedResponse); - -jest.mock('@novu/client', () => ({ - ...jest.requireActual('@novu/client'), - ApiService: jest.fn().mockImplementation(() => { - const apiService = { - isAuthenticated: false, - initializeSession, - setAuthorizationToken: jest.fn(() => { - apiService.isAuthenticated = true; - }), - getNotificationsList, - }; - - return apiService; - }), -})); - -describe('Novu', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('lazy session initialization', () => { - test('should call the queued feeds.fetch after the session is initialized', async () => { - const page = 1; - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - const res = await novu.feeds.fetch({ page }); - - expect(initializeSession).toHaveBeenCalledTimes(1); - expect(getNotificationsList).toHaveBeenCalledWith(page, {}); - expect(res).toEqual(mockFeedResponse); - }); - - test('should call the feeds.fetch right away when session is already initialized', async () => { - const page = 1; - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - // await for session initialization - await new Promise((resolve) => setTimeout(resolve, 10)); - - const res = await novu.feeds.fetch({ page }); - - expect(initializeSession).toHaveBeenCalledTimes(1); - expect(getNotificationsList).toHaveBeenCalledWith(page, {}); - expect(res).toEqual(mockFeedResponse); - }); - - test('should reject the queued feeds.fetch if session initialization fails', async () => { - const page = 1; - const error = new Error('Failed to initialize session'); - initializeSession.mockRejectedValueOnce(error); - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - - const fetchPromise = novu.feeds.fetch({ page }); - - await expect(fetchPromise).rejects.toEqual(error); - }); - - test('should reject the feeds.fetch right away when session initialization has failed', async () => { - const page = 1; - const error = new Error('Failed to initialize session'); - initializeSession.mockRejectedValueOnce(error); - const novu = new Novu({ applicationIdentifier: 'applicationIdentifier', subscriberId: 'subscriberId' }); - // await for session initialization - await new Promise((resolve) => setTimeout(resolve, 10)); - - const fetchPromise = novu.feeds.fetch({ page }); - - await expect(fetchPromise).rejects.toEqual(error); - }); - }); -}); From bd17c58589b91f98e592f007f58974f4a0b2d8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Thu, 6 Jun 2024 17:22:10 +0200 Subject: [PATCH 11/14] feat(js): js sdk feeds module --- packages/js/src/novu.ts | 3 - packages/js/src/utils/base-module.ts | 48 --------- pnpm-lock.yaml | 144 +++++++++++++++------------ 3 files changed, 79 insertions(+), 116 deletions(-) delete mode 100644 packages/js/src/utils/base-module.ts diff --git a/packages/js/src/novu.ts b/packages/js/src/novu.ts index 313129298cc..8f07cd6bc8d 100644 --- a/packages/js/src/novu.ts +++ b/packages/js/src/novu.ts @@ -1,5 +1,3 @@ -import { ApiService } from '@novu/client'; - import { NovuEventEmitter } from './event-emitter'; import type { EventHandler, EventNames, Events } from './event-emitter'; import { Feeds } from './feeds'; @@ -19,7 +17,6 @@ type NovuOptions = { export class Novu implements Pick { #emitter: NovuEventEmitter; #session: Session; - #apiService: ApiService; public readonly feeds: Feeds; public readonly preferences: Preferences; diff --git a/packages/js/src/utils/base-module.ts b/packages/js/src/utils/base-module.ts deleted file mode 100644 index fe170266a46..00000000000 --- a/packages/js/src/utils/base-module.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ApiService } from 'client/dist/cjs'; -import { NovuEventEmitter } from '../event-emitter'; - -interface CallQueueItem { - fn: () => Promise; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - resolve: (value: any | PromiseLike) => void; - reject: (reason?: unknown) => void; -} - -export class BaseModule { - _apiService: ApiService; - _emitter: NovuEventEmitter; - #callsQueue: CallQueueItem[] = []; - #sessionError: unknown; - - constructor(emitter: NovuEventEmitter, apiService: ApiService) { - this._emitter = emitter; - this._apiService = apiService; - this._emitter.on('session.initialize.success', () => { - this.#callsQueue.forEach(async ({ fn, resolve }) => { - resolve(await fn()); - }); - this.#callsQueue = []; - }); - this._emitter.on('session.initialize.error', ({ error }) => { - this.#sessionError = error; - this.#callsQueue.forEach(({ reject }) => { - reject(error); - }); - this.#callsQueue = []; - }); - } - - async callWithSession(fn: () => Promise): Promise { - if (this._apiService.isAuthenticated) { - return fn(); - } - - if (this.#sessionError) { - return Promise.reject(this.#sessionError); - } - - return new Promise(async (resolve, reject) => { - this.#callsQueue.push({ fn, resolve, reject }); - }); - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7c268cf647..b6ac189e585 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31839,8 +31839,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0 - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/core': 3.575.0 '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 @@ -32266,6 +32266,52 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.575.0 + '@aws-sdk/core': 3.575.0 + '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/middleware-host-header': 3.575.0 + '@aws-sdk/middleware-logger': 3.575.0 + '@aws-sdk/middleware-recursion-detection': 3.575.0 + '@aws-sdk/middleware-user-agent': 3.575.0 + '@aws-sdk/region-config-resolver': 3.575.0 + '@aws-sdk/types': 3.575.0 + '@aws-sdk/util-endpoints': 3.575.0 + '@aws-sdk/util-user-agent-browser': 3.575.0 + '@aws-sdk/util-user-agent-node': 3.575.0 + '@smithy/config-resolver': 3.0.0 + '@smithy/core': 2.0.0 + '@smithy/fetch-http-handler': 3.0.0 + '@smithy/hash-node': 3.0.0 + '@smithy/invalid-dependency': 3.0.0 + '@smithy/middleware-content-length': 3.0.0 + '@smithy/middleware-endpoint': 3.0.0 + '@smithy/middleware-retry': 3.0.0 + '@smithy/middleware-serde': 3.0.0 + '@smithy/middleware-stack': 3.0.0 + '@smithy/node-config-provider': 3.0.0 + '@smithy/node-http-handler': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.0.0 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.0 + '@smithy/util-defaults-mode-node': 3.0.0 + '@smithy/util-endpoints': 2.0.0 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-retry': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sts' + - aws-crt + '@aws-sdk/client-sso@3.382.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 @@ -32564,52 +32610,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sso-oidc': 3.575.0 '@aws-sdk/core': 3.575.0 - '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0) - '@aws-sdk/middleware-host-header': 3.575.0 - '@aws-sdk/middleware-logger': 3.575.0 - '@aws-sdk/middleware-recursion-detection': 3.575.0 - '@aws-sdk/middleware-user-agent': 3.575.0 - '@aws-sdk/region-config-resolver': 3.575.0 - '@aws-sdk/types': 3.575.0 - '@aws-sdk/util-endpoints': 3.575.0 - '@aws-sdk/util-user-agent-browser': 3.575.0 - '@aws-sdk/util-user-agent-node': 3.575.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.0 - '@smithy/fetch-http-handler': 3.0.0 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.0 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.0 - '@smithy/util-defaults-mode-node': 3.0.0 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.575.0 - '@aws-sdk/core': 3.575.0 - '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)) + '@aws-sdk/credential-provider-node': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0) '@aws-sdk/middleware-host-header': 3.575.0 '@aws-sdk/middleware-logger': 3.575.0 '@aws-sdk/middleware-recursion-detection': 3.575.0 @@ -32646,7 +32647,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/core@3.496.0': @@ -32782,13 +32782,13 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0))': + '@aws-sdk/credential-provider-ini@3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0)': dependencies: - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) + '@aws-sdk/client-sts': 3.575.0 '@aws-sdk/credential-provider-env': 3.575.0 '@aws-sdk/credential-provider-process': 3.575.0 - '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) - '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)) + '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)) + '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0) '@aws-sdk/types': 3.575.0 '@smithy/credential-provider-imds': 3.0.0 '@smithy/property-provider': 3.0.0 @@ -32866,14 +32866,14 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-node@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0))': + '@aws-sdk/credential-provider-node@3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0)': dependencies: '@aws-sdk/credential-provider-env': 3.575.0 '@aws-sdk/credential-provider-http': 3.575.0 - '@aws-sdk/credential-provider-ini': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0)(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)) + '@aws-sdk/credential-provider-ini': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))(@aws-sdk/client-sts@3.575.0) '@aws-sdk/credential-provider-process': 3.575.0 - '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) - '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)) + '@aws-sdk/credential-provider-sso': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)) + '@aws-sdk/credential-provider-web-identity': 3.575.0(@aws-sdk/client-sts@3.575.0) '@aws-sdk/types': 3.575.0 '@smithy/credential-provider-imds': 3.0.0 '@smithy/property-provider': 3.0.0 @@ -32975,6 +32975,19 @@ snapshots: - aws-crt optional: true + '@aws-sdk/credential-provider-sso@3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))': + dependencies: + '@aws-sdk/client-sso': 3.575.0 + '@aws-sdk/token-providers': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0)) + '@aws-sdk/types': 3.575.0 + '@smithy/property-provider': 3.0.0 + '@smithy/shared-ini-file-loader': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + '@aws-sdk/credential-provider-sso@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: '@aws-sdk/client-sso': 3.575.0 @@ -33014,14 +33027,6 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0(@aws-sdk/client-sso-oidc@3.575.0))': - dependencies: - '@aws-sdk/client-sts': 3.575.0(@aws-sdk/client-sso-oidc@3.575.0) - '@aws-sdk/types': 3.575.0 - '@smithy/property-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - '@aws-sdk/credential-provider-web-identity@3.575.0(@aws-sdk/client-sts@3.575.0)': dependencies: '@aws-sdk/client-sts': 3.575.0 @@ -33446,6 +33451,15 @@ snapshots: - aws-crt optional: true + '@aws-sdk/token-providers@3.575.0(@aws-sdk/client-sso-oidc@3.575.0(@aws-sdk/client-sts@3.575.0))': + dependencies: + '@aws-sdk/client-sso-oidc': 3.575.0(@aws-sdk/client-sts@3.575.0) + '@aws-sdk/types': 3.575.0 + '@smithy/property-provider': 3.0.0 + '@smithy/shared-ini-file-loader': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + '@aws-sdk/token-providers@3.575.0(@aws-sdk/client-sso-oidc@3.575.0)': dependencies: '@aws-sdk/client-sso-oidc': 3.575.0 From a22b888dc141aa57bbaa1a22761116b83f298587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Mon, 10 Jun 2024 11:23:49 +0200 Subject: [PATCH 12/14] feat(js): js sdk preferences --- packages/client/src/api/api.service.ts | 15 +++++ packages/js/src/event-emitter/types.ts | 8 ++- packages/js/src/feeds/notification.ts | 31 +++++------ packages/js/src/preferences/helpers.ts | 64 ++++++++++++++++++++++ packages/js/src/preferences/preference.ts | 38 +++++++++++++ packages/js/src/preferences/preferences.ts | 31 ++++++++++- packages/js/src/preferences/types.ts | 11 ++++ packages/js/src/types.ts | 42 ++++++++++++++ 8 files changed, 221 insertions(+), 19 deletions(-) create mode 100644 packages/js/src/preferences/helpers.ts create mode 100644 packages/js/src/preferences/preference.ts create mode 100644 packages/js/src/preferences/types.ts diff --git a/packages/client/src/api/api.service.ts b/packages/client/src/api/api.service.ts index 1ace0890580..51e02145a85 100644 --- a/packages/client/src/api/api.service.ts +++ b/packages/client/src/api/api.service.ts @@ -6,6 +6,7 @@ import { ISessionDto, INotificationDto, MarkMessagesAsEnum, + PreferenceLevelEnum, } from '@novu/shared'; import { HttpClient } from '../http-client'; import { @@ -206,14 +207,28 @@ export class ApiService { return this.httpClient.get('/widgets/organization'); } + /** + * @deprecated use getPreferences instead + */ async getUserPreference(): Promise { return this.httpClient.get('/widgets/preferences'); } + /** + * @deprecated use getPreferences instead + */ async getUserGlobalPreference(): Promise { return this.httpClient.get('/widgets/preferences/global'); } + async getPreferences({ + level = PreferenceLevelEnum.TEMPLATE, + }: { + level?: `${PreferenceLevelEnum}`; + }): Promise> { + return this.httpClient.get(`/widgets/preferences/${level}`); + } + async updateSubscriberPreference( templateId: string, channelType: string, diff --git a/packages/js/src/event-emitter/types.ts b/packages/js/src/event-emitter/types.ts index 4ab5c85c07c..4c1930c3391 100644 --- a/packages/js/src/event-emitter/types.ts +++ b/packages/js/src/event-emitter/types.ts @@ -10,6 +10,8 @@ import type { RemoveAllNotificationsArgs, RemoveNotificationsArgs, } from '../feeds'; +import { Preference } from '../preferences/preference'; +import { FetchPreferencesArgs, UpdatePreferencesArgs } from '../preferences/types'; import type { InitializeSessionArgs } from '../session'; import type { PaginatedResponse, Session } from '../types'; @@ -90,6 +92,8 @@ type NotificationRemoveEvents = BaseEvents< Notification, Notification >; +type PreferencesFetchEvents = BaseEvents<'preferences.fetch', FetchPreferencesArgs, Preference[]>; +type PreferencesUpdateEvents = BaseEvents<'preferences.update', UpdatePreferencesArgs, Preference>; /** * Events that are emitted by Novu Event Emitter. @@ -113,7 +117,9 @@ export type Events = SessionInitializeEvents & FeedRemoveAllNotificationsEvents & NotificationMarkAsEvents & NotificationMarkActionAsEvents & - NotificationRemoveEvents; + NotificationRemoveEvents & + PreferencesFetchEvents & + PreferencesUpdateEvents; export type EventNames = keyof Events; diff --git a/packages/js/src/feeds/notification.ts b/packages/js/src/feeds/notification.ts index 79b5135f35b..f2a95019ac0 100644 --- a/packages/js/src/feeds/notification.ts +++ b/packages/js/src/feeds/notification.ts @@ -22,7 +22,6 @@ type NotificationLike = Pick< | 'actor' | 'subscriber' | 'transactionId' - | 'templateIdentifier' | 'content' | 'read' | 'seen' @@ -36,21 +35,20 @@ export class Notification implements Pick { #emitter: NovuEventEmitter; #apiService: ApiService; - _id: string; - _feedId?: string | null; - createdAt: string; - updatedAt: string; - actor?: Actor; - subscriber?: Subscriber; - transactionId: string; - templateIdentifier: string; - content: string; - read: boolean; - seen: boolean; - deleted: boolean; - cta: Cta; - payload: Record; - overrides: Record; + readonly _id: string; + readonly _feedId?: string | null; + readonly createdAt: string; + readonly updatedAt: string; + readonly actor?: Actor; + readonly subscriber?: Subscriber; + readonly transactionId: string; + readonly content: string; + readonly read: boolean; + readonly seen: boolean; + readonly deleted: boolean; + readonly cta: Cta; + readonly payload: Record; + readonly overrides: Record; constructor(notification: NotificationLike) { this.#emitter = NovuEventEmitter.getInstance(); @@ -63,7 +61,6 @@ export class Notification implements Pick { this.actor = notification.actor; this.subscriber = notification.subscriber; this.transactionId = notification.transactionId; - this.templateIdentifier = notification.templateIdentifier; this.content = notification.content; this.read = notification.read; this.seen = notification.seen; diff --git a/packages/js/src/preferences/helpers.ts b/packages/js/src/preferences/helpers.ts new file mode 100644 index 00000000000..5cc862f54f4 --- /dev/null +++ b/packages/js/src/preferences/helpers.ts @@ -0,0 +1,64 @@ +import { ApiService } from '@novu/client'; + +import type { NovuEventEmitter } from '../event-emitter'; +import type { ChannelPreferenceOverride, TODO } from '../types'; +import { PreferenceLevel } from '../types'; +import { Preference } from './preference'; +import type { UpdatePreferencesArgs } from './types'; + +export const mapPreference = (apiPreference: { + template?: TODO; + preference: { + enabled: boolean; + channels: { + email?: boolean; + sms?: boolean; + in_app?: boolean; + chat?: boolean; + push?: boolean; + }; + overrides?: ChannelPreferenceOverride[]; + }; +}): Preference => { + const { template: workflow, preference } = apiPreference; + const hasWorkflow = workflow !== undefined; + const level = hasWorkflow ? PreferenceLevel.TEMPLATE : PreferenceLevel.GLOBAL; + + return new Preference({ + level, + enabled: preference.enabled, + channels: preference.channels, + workflow, + overrides: preference.overrides, + }); +}; + +export const updatePreference = async ({ + emitter, + apiService, + args, +}: { + emitter: NovuEventEmitter; + apiService: ApiService; + args: UpdatePreferencesArgs; +}): Promise => { + const { workflowId, enabled, channel } = args; + try { + emitter.emit('preferences.update.pending', { args }); + + let response; + if (workflowId) { + response = await apiService.updateSubscriberPreference(workflowId, channel, enabled); + } else { + response = await apiService.updateSubscriberGlobalPreference([{ channelType: channel, enabled }]); + } + + const preference = new Preference(mapPreference(response)); + emitter.emit('preferences.update.success', { args, result: preference }); + + return preference; + } catch (error) { + emitter.emit('preferences.update.error', { args, error }); + throw error; + } +}; diff --git a/packages/js/src/preferences/preference.ts b/packages/js/src/preferences/preference.ts new file mode 100644 index 00000000000..38ab062750b --- /dev/null +++ b/packages/js/src/preferences/preference.ts @@ -0,0 +1,38 @@ +import { ApiService } from '@novu/client'; + +import { NovuEventEmitter } from '../event-emitter'; +import { ChannelPreference, ChannelPreferenceOverride, ChannelType, PreferenceLevel, WorkflowInfo } from '../types'; +import { ApiServiceSingleton } from '../utils/api-service-signleton'; +import { updatePreference } from './helpers'; + +type PreferenceLike = Pick; + +export class Preference { + #emitter: NovuEventEmitter; + #apiService: ApiService; + + readonly level: PreferenceLevel; + readonly enabled: boolean; + readonly channels: ChannelPreference; + readonly workflow?: WorkflowInfo; + readonly overrides?: ChannelPreferenceOverride[]; + + constructor(preference: PreferenceLike) { + this.#emitter = NovuEventEmitter.getInstance(); + this.#apiService = ApiServiceSingleton.getInstance(); + + this.level = preference.level; + this.enabled = preference.enabled; + this.channels = preference.channels; + this.workflow = preference.workflow; + this.overrides = preference.overrides; + } + + updatePreference({ enabled, channel }: { enabled: boolean; channel: ChannelType }): Promise { + return updatePreference({ + emitter: this.#emitter, + apiService: this.#apiService, + args: { workflowId: this.workflow?._id, enabled, channel }, + }); + } +} diff --git a/packages/js/src/preferences/preferences.ts b/packages/js/src/preferences/preferences.ts index 8ca3d6274b1..8626d74ed28 100644 --- a/packages/js/src/preferences/preferences.ts +++ b/packages/js/src/preferences/preferences.ts @@ -1,3 +1,32 @@ import { BaseModule } from '../base-module'; +import { PreferenceLevel } from '../types'; +import { mapPreference, updatePreference } from './helpers'; +import { Preference } from './preference'; +import type { FetchPreferencesArgs, UpdatePreferencesArgs } from './types'; -export class Preferences extends BaseModule {} +export class Preferences extends BaseModule { + async fetch({ level = PreferenceLevel.TEMPLATE }: FetchPreferencesArgs = {}): Promise { + return this.callWithSession(async () => { + const args = { level }; + try { + this._emitter.emit('preferences.fetch.pending', { args }); + + const response = await this._apiService.getPreferences({ level }); + const modifiedResponse: Preference[] = response.map((el) => new Preference(mapPreference(el))); + + this._emitter.emit('preferences.fetch.success', { args, result: modifiedResponse }); + + return modifiedResponse; + } catch (error) { + this._emitter.emit('preferences.fetch.error', { args, error }); + throw error; + } + }); + } + + async update(args: UpdatePreferencesArgs): Promise { + return this.callWithSession(async () => + updatePreference({ emitter: this._emitter, apiService: this._apiService, args }) + ); + } +} diff --git a/packages/js/src/preferences/types.ts b/packages/js/src/preferences/types.ts new file mode 100644 index 00000000000..4d24307ae8e --- /dev/null +++ b/packages/js/src/preferences/types.ts @@ -0,0 +1,11 @@ +import { ChannelType, PreferenceLevel } from '../types'; + +export type FetchPreferencesArgs = { + level?: PreferenceLevel; +}; + +export type UpdatePreferencesArgs = { + workflowId?: string; + enabled: boolean; + channel: ChannelType; +}; diff --git a/packages/js/src/types.ts b/packages/js/src/types.ts index 49d1d708f3f..60a6d2f3439 100644 --- a/packages/js/src/types.ts +++ b/packages/js/src/types.ts @@ -26,6 +26,25 @@ export enum CtaType { REDIRECT = 'redirect', } +export enum PreferenceLevel { + GLOBAL = 'global', + TEMPLATE = 'template', +} + +export enum ChannelType { + IN_APP = 'in_app', + EMAIL = 'email', + SMS = 'sms', + CHAT = 'chat', + PUSH = 'push', +} + +export enum PreferenceOverrideSource { + SUBSCRIBER = 'subscriber', + TEMPLATE = 'template', + WORKFLOW_OVERRIDE = 'workflowOverride', +} + export type Session = { token: string; profile: { @@ -99,6 +118,29 @@ export type Cta = { action?: MessageAction; }; +export type WorkflowInfo = { + _id: string; + name: string; + critical: boolean; + tags?: string[]; + identifier: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data?: Record; +}; + +export type ChannelPreference = { + email?: boolean; + sms?: boolean; + in_app?: boolean; + chat?: boolean; + push?: boolean; +}; + +export type ChannelPreferenceOverride = { + channel: ChannelType; + source: PreferenceOverrideSource; +}; + export type PaginatedResponse = { data: T[]; hasMore: boolean; From 68987c2d5fe6244a634170d6faf25295c63957fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Tue, 11 Jun 2024 13:23:47 +0200 Subject: [PATCH 13/14] chore(js): simplified the notification and preference classes --- packages/js/.eslintrc.js | 20 --------- packages/js/src/feeds/feeds.ts | 11 ++++- packages/js/src/feeds/helpers.ts | 29 +++++++++---- packages/js/src/feeds/notification.ts | 51 +++++------------------ packages/js/src/preferences/helpers.ts | 14 +++++-- packages/js/src/preferences/preference.ts | 12 +++--- packages/js/src/types.ts | 16 +++---- 7 files changed, 60 insertions(+), 93 deletions(-) delete mode 100644 packages/js/.eslintrc.js diff --git a/packages/js/.eslintrc.js b/packages/js/.eslintrc.js deleted file mode 100644 index b4d6b44750e..00000000000 --- a/packages/js/.eslintrc.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], - rules: { - '@typescript-eslint/explicit-module-boundary-types': 'error', - '@typescript-eslint/naming-convention': [ - 'error', - { - filter: '_', - selector: 'variableLike', - leadingUnderscore: 'allow', - format: ['PascalCase', 'camelCase', 'UPPER_CASE'], - }, - ], - }, - parserOptions: { - project: './tsconfig.json', - ecmaVersion: 2020, - sourceType: 'module', - }, -}; diff --git a/packages/js/src/feeds/feeds.ts b/packages/js/src/feeds/feeds.ts index 4cbbaef9d33..dbc6b976f14 100644 --- a/packages/js/src/feeds/feeds.ts +++ b/packages/js/src/feeds/feeds.ts @@ -23,7 +23,14 @@ import type { MarkNotificationActionAsByInstanceArgs, } from './types'; import { READ_OR_UNREAD, SEEN_OR_UNSEEN } from '../utils/notification-utils'; -import { markActionAs, markNotificationAs, markNotificationsAs, remove, removeNotifications } from './helpers'; +import { + mapFromApiNotification, + markActionAs, + markNotificationAs, + markNotificationsAs, + remove, + removeNotifications, +} from './helpers'; export class Feeds extends BaseModule { async fetch({ page = 0, status, ...restOptions }: FetchFeedArgs = {}): Promise> { @@ -39,7 +46,7 @@ export class Feeds extends BaseModule { }); const modifiedResponse: PaginatedResponse = { ...response, - data: response.data.map((el) => new Notification(el as TODO)), + data: response.data.map((el) => new Notification(mapFromApiNotification(el as TODO))), }; this._emitter.emit('feeds.fetch.success', { args, result: modifiedResponse }); diff --git a/packages/js/src/feeds/helpers.ts b/packages/js/src/feeds/helpers.ts index 2cf0872b5ba..6cea962a72b 100644 --- a/packages/js/src/feeds/helpers.ts +++ b/packages/js/src/feeds/helpers.ts @@ -11,6 +11,19 @@ import { RemoveNotificationsArgs, } from './types'; +export const mapFromApiNotification = (apiNotification: TODO): Notification => + new Notification({ + id: apiNotification.id, + feedIdentifier: apiNotification._feedId, + createdAt: apiNotification.createdAt, + avatar: apiNotification.actor, + body: apiNotification.content, + read: apiNotification.read, + seen: apiNotification.seen, + deleted: apiNotification.deleted, + cta: apiNotification.cta, + }); + const getOptimisticMarkAs = (status: NotificationStatus): Partial => { switch (status) { case NotificationStatus.READ: @@ -49,7 +62,7 @@ export const markNotificationAs = async ({ args: MarkNotificationAsArgs; }): Promise => { const isNotification = typeof notification !== 'undefined'; - const notificationId = isNotification ? notification._id : id ?? ''; + const notificationId = isNotification ? notification.id : id ?? ''; const args = { id, notification, status }; try { emitter.emit('notification.mark_as.pending', { @@ -61,7 +74,7 @@ export const markNotificationAs = async ({ messageId: [notificationId], markAs: status, }); - const updatedNotification = new Notification(response[0] as TODO); + const updatedNotification = new Notification(mapFromApiNotification(response[0] as TODO)); emitter.emit('notification.mark_as.success', { args, result: updatedNotification }); @@ -121,7 +134,7 @@ export const markActionAs = async ({ args: MarkNotificationActionAsArgs; }): Promise => { const isNotification = typeof notification !== 'undefined'; - const notificationId = isNotification ? notification._id : id ?? ''; + const notificationId = isNotification ? notification.id : id ?? ''; const args = { id, notification, button, status }; try { emitter.emit('notification.mark_action_as.pending', { @@ -132,7 +145,7 @@ export const markActionAs = async ({ }); const response = await apiService.updateAction(notificationId, button, status); - const updatedNotification = new Notification(response as TODO); + const updatedNotification = new Notification(mapFromApiNotification(response as TODO)); emitter.emit('notification.mark_action_as.success', { args, @@ -162,7 +175,7 @@ export const remove = async ({ args: RemoveNotificationArgs; }): Promise => { const isNotification = typeof notification !== 'undefined'; - const notificationId = isNotification ? notification._id : id ?? ''; + const notificationId = isNotification ? notification.id : id ?? ''; const args = { id, notification }; try { const deletedNotification = isNotification @@ -205,7 +218,7 @@ export const removeNotifications = async ({ : undefined; emitter.emit('feeds.remove_notifications.pending', { args, optimistic: optimisticNotifications }); - const notificationIds = isNotificationArray ? notifications.map((el) => el._id) : ids ?? []; + const notificationIds = isNotificationArray ? notifications.map((el) => el.id) : ids ?? []; await apiService.removeMessages(notificationIds); emitter.emit('feeds.remove_notifications.success', { args, result: optimisticNotifications }); @@ -239,14 +252,14 @@ export const markNotificationsAs = async ({ emitter.emit('feeds.mark_notifications_as.pending', { args, optimistic: optimisticNotifications }); const notificationIds = isNotificationArray - ? notifications.map((el) => (typeof el === 'string' ? el : el._id)) + ? notifications.map((el) => (typeof el === 'string' ? el : el.id)) : ids ?? []; const response = await apiService.markMessagesAs({ messageId: notificationIds, markAs: status, }); - const updatedNotifications = response.map((el) => new Notification(el as TODO)); + const updatedNotifications = response.map((el) => new Notification(mapFromApiNotification(el as TODO))); emitter.emit('feeds.mark_notifications_as.success', { args, result: updatedNotifications, diff --git a/packages/js/src/feeds/notification.ts b/packages/js/src/feeds/notification.ts index f2a95019ac0..ab75b9200ba 100644 --- a/packages/js/src/feeds/notification.ts +++ b/packages/js/src/feeds/notification.ts @@ -1,73 +1,42 @@ import { ApiService } from '@novu/client'; import { EventHandler, EventNames, Events, NovuEventEmitter } from '../event-emitter'; -import { - Actor, - NotificationActionStatus, - NotificationButton, - Cta, - NotificationStatus, - Subscriber, - TODO, -} from '../types'; +import { Avatar, NotificationActionStatus, NotificationButton, Cta, NotificationStatus, TODO } from '../types'; import { ApiServiceSingleton } from '../utils/api-service-singleton'; import { markActionAs, markNotificationAs, remove } from './helpers'; type NotificationLike = Pick< Notification, - | '_id' - | '_feedId' - | 'createdAt' - | 'updatedAt' - | 'actor' - | 'subscriber' - | 'transactionId' - | 'content' - | 'read' - | 'seen' - | 'deleted' - | 'cta' - | 'payload' - | 'overrides' + 'id' | 'feedIdentifier' | 'createdAt' | 'avatar' | 'body' | 'read' | 'seen' | 'deleted' | 'cta' >; export class Notification implements Pick { #emitter: NovuEventEmitter; #apiService: ApiService; - readonly _id: string; - readonly _feedId?: string | null; + readonly id: string; + readonly feedIdentifier?: string | null; readonly createdAt: string; - readonly updatedAt: string; - readonly actor?: Actor; - readonly subscriber?: Subscriber; - readonly transactionId: string; - readonly content: string; + readonly avatar?: Avatar; + readonly body: string; readonly read: boolean; readonly seen: boolean; readonly deleted: boolean; readonly cta: Cta; - readonly payload: Record; - readonly overrides: Record; constructor(notification: NotificationLike) { this.#emitter = NovuEventEmitter.getInstance(); this.#apiService = ApiServiceSingleton.getInstance(); - this._id = notification._id; - this._feedId = notification._feedId; + this.id = notification.id; + this.feedIdentifier = notification.feedIdentifier; this.createdAt = notification.createdAt; - this.updatedAt = notification.updatedAt; - this.actor = notification.actor; - this.subscriber = notification.subscriber; - this.transactionId = notification.transactionId; - this.content = notification.content; + this.avatar = notification.avatar; + this.body = notification.body; this.read = notification.read; this.seen = notification.seen; this.deleted = notification.deleted; this.cta = notification.cta; - this.payload = notification.payload; - this.overrides = notification.overrides; } markAsRead(): Promise { diff --git a/packages/js/src/preferences/helpers.ts b/packages/js/src/preferences/helpers.ts index 5cc862f54f4..46a510db7d8 100644 --- a/packages/js/src/preferences/helpers.ts +++ b/packages/js/src/preferences/helpers.ts @@ -1,7 +1,7 @@ import { ApiService } from '@novu/client'; import type { NovuEventEmitter } from '../event-emitter'; -import type { ChannelPreferenceOverride, TODO } from '../types'; +import type { TODO } from '../types'; import { PreferenceLevel } from '../types'; import { Preference } from './preference'; import type { UpdatePreferencesArgs } from './types'; @@ -17,7 +17,6 @@ export const mapPreference = (apiPreference: { chat?: boolean; push?: boolean; }; - overrides?: ChannelPreferenceOverride[]; }; }): Preference => { const { template: workflow, preference } = apiPreference; @@ -28,8 +27,15 @@ export const mapPreference = (apiPreference: { level, enabled: preference.enabled, channels: preference.channels, - workflow, - overrides: preference.overrides, + workflow: hasWorkflow + ? { + id: workflow?._id, + name: workflow?.name, + critical: workflow?.critical, + identifier: workflow?.identifier, + data: workflow?.data, + } + : undefined, }); }; diff --git a/packages/js/src/preferences/preference.ts b/packages/js/src/preferences/preference.ts index 38ab062750b..b4145bff42d 100644 --- a/packages/js/src/preferences/preference.ts +++ b/packages/js/src/preferences/preference.ts @@ -1,11 +1,11 @@ import { ApiService } from '@novu/client'; import { NovuEventEmitter } from '../event-emitter'; -import { ChannelPreference, ChannelPreferenceOverride, ChannelType, PreferenceLevel, WorkflowInfo } from '../types'; -import { ApiServiceSingleton } from '../utils/api-service-signleton'; +import { ChannelPreference, ChannelType, PreferenceLevel, Workflow } from '../types'; +import { ApiServiceSingleton } from '../utils/api-service-singleton'; import { updatePreference } from './helpers'; -type PreferenceLike = Pick; +type PreferenceLike = Pick; export class Preference { #emitter: NovuEventEmitter; @@ -14,8 +14,7 @@ export class Preference { readonly level: PreferenceLevel; readonly enabled: boolean; readonly channels: ChannelPreference; - readonly workflow?: WorkflowInfo; - readonly overrides?: ChannelPreferenceOverride[]; + readonly workflow?: Workflow; constructor(preference: PreferenceLike) { this.#emitter = NovuEventEmitter.getInstance(); @@ -25,14 +24,13 @@ export class Preference { this.enabled = preference.enabled; this.channels = preference.channels; this.workflow = preference.workflow; - this.overrides = preference.overrides; } updatePreference({ enabled, channel }: { enabled: boolean; channel: ChannelType }): Promise { return updatePreference({ emitter: this.#emitter, apiService: this.#apiService, - args: { workflowId: this.workflow?._id, enabled, channel }, + args: { workflowId: this.workflow?.id, enabled, channel }, }); } } diff --git a/packages/js/src/types.ts b/packages/js/src/types.ts index 60a6d2f3439..180d0f62b03 100644 --- a/packages/js/src/types.ts +++ b/packages/js/src/types.ts @@ -15,7 +15,7 @@ export enum NotificationActionStatus { DONE = 'done', } -export enum ActorType { +export enum AvatarType { NONE = 'none', USER = 'user', SYSTEM_ICON = 'system_icon', @@ -59,8 +59,8 @@ export type Session = { }; }; -export type Actor = { - type: ActorType; +export type Avatar = { + type: AvatarType; data: string | null; }; @@ -118,11 +118,10 @@ export type Cta = { action?: MessageAction; }; -export type WorkflowInfo = { - _id: string; +export type Workflow = { + id: string; name: string; critical: boolean; - tags?: string[]; identifier: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: Record; @@ -136,11 +135,6 @@ export type ChannelPreference = { push?: boolean; }; -export type ChannelPreferenceOverride = { - channel: ChannelType; - source: PreferenceOverrideSource; -}; - export type PaginatedResponse = { data: T[]; hasMore: boolean; From 8c6464a1f17c9b33fc99b6e5bfba0edb87b76d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Thu, 20 Jun 2024 15:57:00 +0200 Subject: [PATCH 14/14] feat(js): temporarily increased bundle size limits --- packages/js/scripts/size-limit.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/js/scripts/size-limit.js b/packages/js/scripts/size-limit.js index 5889f518c55..1fb85417c8b 100644 --- a/packages/js/scripts/size-limit.js +++ b/packages/js/scripts/size-limit.js @@ -15,14 +15,14 @@ const modules = [ { name: 'UMD minified', filePath: umdPath, - limit: '10 kb', - limitInBytes: 20_000, + limit: '90 kb', + limitInBytes: 90_000, }, { name: 'UMD gzip', filePath: umdGzipPath, - limit: '10 kb', - limitInBytes: 10_000, + limit: '25 kb', + limitInBytes: 25_000, }, ];