-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(js): handling the web socket connection and events #5704
Changes from all commits
1f0e5b7
880de43
f37937b
1102abd
bf034f9
4a0308f
4e74481
fbea11b
67c85cb
4167350
b9abbae
bce4acf
3a15212
acb3717
26ee19b
ebc1d38
79c9757
d405bfb
bc2510d
3329087
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { | ||
import type { | ||
IPreferenceChannels, | ||
NotificationTemplateCustomData, | ||
} from '@novu/shared'; | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -15,14 +15,12 @@ const modules = [ | |||||
{ | ||||||
name: 'UMD minified', | ||||||
filePath: umdPath, | ||||||
limit: '90 kb', | ||||||
limitInBytes: 90_000, | ||||||
limitInBytes: 70_000, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the current size?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
}, | ||||||
{ | ||||||
name: 'UMD gzip', | ||||||
filePath: umdGzipPath, | ||||||
limit: '25 kb', | ||||||
limitInBytes: 25_000, | ||||||
limitInBytes: 20_000, | ||||||
}, | ||||||
]; | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { ApiService } from '@novu/client'; | ||
import type { ApiService } from '@novu/client'; | ||
|
||
import { NovuEventEmitter } from './event-emitter'; | ||
import { Session } from './types'; | ||
import { ApiServiceSingleton } from './utils/api-service-singleton'; | ||
|
||
interface CallQueueItem { | ||
|
@@ -19,13 +20,15 @@ export class BaseModule { | |
constructor() { | ||
this._emitter = NovuEventEmitter.getInstance(); | ||
this._apiService = ApiServiceSingleton.getInstance(); | ||
this._emitter.on('session.initialize.success', () => { | ||
this._emitter.on('session.initialize.success', ({ result }) => { | ||
this.onSessionSuccess(result); | ||
this.#callsQueue.forEach(async ({ fn, resolve }) => { | ||
resolve(await fn()); | ||
}); | ||
this.#callsQueue = []; | ||
}); | ||
this._emitter.on('session.initialize.error', ({ error }) => { | ||
this.onSessionError(error); | ||
this.#sessionError = error; | ||
this.#callsQueue.forEach(({ reject }) => { | ||
reject(error); | ||
|
@@ -34,6 +37,10 @@ export class BaseModule { | |
}); | ||
} | ||
|
||
protected onSessionSuccess(_: Session): void {} | ||
|
||
protected onSessionError(_: unknown): void {} | ||
|
||
Comment on lines
+40
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two functions will be overridden by the class that extends this one. |
||
async callWithSession<T>(fn: () => Promise<T>): Promise<T> { | ||
if (this._apiService.isAuthenticated) { | ||
return fn(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,7 @@ export class NovuEventEmitter { | |
} | ||
|
||
off<Key extends EventNames>(eventName: Key, listener: EventHandler<Events[Key]>): void { | ||
this.#mittEmitter.on(eventName, listener); | ||
this.#mittEmitter.off(eventName, listener); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my fault 😅 |
||
} | ||
|
||
emit<Key extends EventNames>(type: Key, event?: Events[Key]): void { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './novu'; | ||
export { Novu } from './novu'; | ||
export { InboxUI } from './ui'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,19 +4,22 @@ import { Feeds } from './feeds'; | |
import { Session } from './session'; | ||
import { Preferences } from './preferences'; | ||
import { ApiServiceSingleton } from './utils/api-service-singleton'; | ||
import { Socket } from './ws'; | ||
|
||
const PRODUCTION_BACKEND_URL = 'https://api.novu.co'; | ||
|
||
type NovuOptions = { | ||
export type NovuOptions = { | ||
applicationIdentifier: string; | ||
subscriberId: string; | ||
subscriberHash?: string; | ||
backendUrl?: string; | ||
socketUrl?: string; | ||
}; | ||
|
||
export class Novu implements Pick<NovuEventEmitter, 'on' | 'off'> { | ||
#emitter: NovuEventEmitter; | ||
#session: Session; | ||
#socket: Socket; | ||
|
||
public readonly feeds: Feeds; | ||
public readonly preferences: Preferences; | ||
|
@@ -32,13 +35,17 @@ export class Novu implements Pick<NovuEventEmitter, 'on' | 'off'> { | |
this.#session.initialize(); | ||
this.feeds = new Feeds(); | ||
this.preferences = new Preferences(); | ||
this.#socket = new Socket({ socketUrl: options.socketUrl }); | ||
} | ||
|
||
on<Key extends EventNames>(eventName: Key, listener: EventHandler<Events[Key]>): void { | ||
if (this.#socket.isSocketEvent(eventName)) { | ||
this.#socket.initialize(); | ||
} | ||
Comment on lines
+42
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When someone tries to listen to one of the WS events then initialize the WebSocket connection and add listeners on events. |
||
this.#emitter.on(eventName, listener); | ||
} | ||
|
||
off<Key extends EventNames>(eventName: Key, listener: EventHandler<Events[Key]>): void { | ||
this.#emitter.on(eventName, listener); | ||
this.#emitter.off(eventName, listener); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { For, createSignal, onMount, type Component } from 'solid-js'; | ||
import { Notification } from '../feeds'; | ||
import { Novu } from '../novu'; | ||
import type { NovuOptions } from '../novu'; | ||
|
||
const Inbox: Component<{ | ||
name: string; | ||
options: NovuOptions; | ||
}> = (props) => { | ||
const [feeds, setFeeds] = createSignal<Notification[]>([]); | ||
|
||
onMount(() => { | ||
const novu = new Novu(props.options); | ||
|
||
// eslint-disable-next-line promise/always-return | ||
novu.feeds.fetch().then((data) => { | ||
setFeeds(data.data); | ||
}); | ||
}); | ||
|
||
return ( | ||
<div> | ||
<header>Hello {props.name} </header> | ||
<For each={feeds()}> | ||
{(feed) => ( | ||
<div> | ||
<h2>{feed.body}</h2> | ||
<p>{feed.createdAt}</p> | ||
</div> | ||
)} | ||
</For> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Inbox; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { render } from 'solid-js/web'; | ||
import Inbox from './Inbox'; | ||
import type { NovuOptions } from '../novu'; | ||
|
||
export class InboxUI { | ||
#dispose: { (): void } | null = null; | ||
#rootElement: HTMLElement; | ||
|
||
mount( | ||
el: HTMLElement, | ||
{ | ||
name = 'novu', | ||
options, | ||
}: { | ||
name?: string; | ||
options: NovuOptions; | ||
} | ||
): void { | ||
if (this.#dispose !== null) { | ||
return; | ||
} | ||
|
||
this.#rootElement = document.createElement('div'); | ||
this.#rootElement.setAttribute('id', 'novu-ui'); | ||
el.appendChild(this.#rootElement); | ||
|
||
const dispose = render(() => <Inbox name={name} options={options} />, this.#rootElement); | ||
|
||
this.#dispose = dispose; | ||
} | ||
|
||
unmount(): void { | ||
this.#dispose?.(); | ||
this.#dispose = null; | ||
this.#rootElement.remove(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use
import type
everywhere in the@novu/client
so that@novu/shared
won't be included in the bundleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💭 thought (non-blocking): If we are just using the types, maybe we can move the
@novu/shared
under thedevDependency
as well.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't because of the types generated for ESM, they are not "copied" during the build time.