@notifi-network/notifi-graphql
Advanced tools
Comparing version 3.0.2-alpha.11 to 3.1.0
@@ -0,0 +0,0 @@ import { gql } from 'graphql-request'; |
@@ -0,0 +0,0 @@ import { gql } from 'graphql-request'; |
@@ -0,0 +0,0 @@ import { gql } from 'graphql-request'; |
@@ -6,3 +6,12 @@ import { gql } from 'graphql-request'; | ||
stateChanged { | ||
__typename | ||
... on TargetStateChangedEvent { | ||
__typename | ||
targetId | ||
targetType | ||
timestamp | ||
} | ||
... on NotificationHistoryStateChangedEvent { | ||
__typename | ||
timestamp | ||
} | ||
} | ||
@@ -9,0 +18,0 @@ } |
@@ -0,0 +0,0 @@ import { ActiveAlertFragment } from '../fragments/ActiveAlertFragment.gql'; |
@@ -1,2 +0,3 @@ | ||
import { Client as WebSocketClient } from 'graphql-ws'; | ||
import { Subscription } from 'relay-runtime'; | ||
import { | ||
@@ -7,22 +8,4 @@ StateChangedEvent, | ||
/** IMPORTANT: the guidelines to remove the event listener: | ||
* 1. For status events (NotifiWebSocketStatusEvents or NotifiSubscriptionStatusEvents), calling removeEventListener will remove the listener. | ||
* 2. For graphql subscription events (NotifiSubscriptionEvents), calling removeEventListener will not unsubscribe the subscription. additionally, you need to call subscription.unsubscribe() to remove the subscription. | ||
*/ | ||
export type NotifiEmitterEvents = NotifiWebSocketStatusEvents & | ||
NotifiSubscriptionEvents & | ||
NotifiSubscriptionStatusEvents; | ||
export type NotifiEmitterEvents = NotifiSubscriptionEvents; | ||
export type NotifiWebSocketStatusEvents = { | ||
wsConnecting: []; | ||
wsConnected: [WebSocketClient]; | ||
wsClosed: [unknown]; // ⬅ The argument is actually the websocket `CloseEvent`, but to avoid bundling DOM typings because the client can run in Node env too, you should assert the websocket type during implementation. https://the-guild.dev/graphql/ws/docs/modules/client#eventclosedlistener | ||
wsError: [Error]; | ||
}; | ||
export type NotifiSubscriptionStatusEvents = { | ||
gqlSubscriptionError: [Error]; | ||
gqlComplete: []; | ||
}; | ||
export type NotifiSubscriptionEvents = { | ||
@@ -33,30 +16,44 @@ tenantActiveAlertChanged: [TenantActiveAlertChangeEvent]; | ||
export type EventCallback< | ||
export type ListenerPayload< | ||
T extends Record<string, any[]>, | ||
K extends keyof T, | ||
> = (...args: T[K]) => void; | ||
> = { | ||
callback: (...args: T[K]) => void; | ||
subscription: Subscription; | ||
}; | ||
export class NotifiEventEmitter<T extends Record<string, Array<any>>> { | ||
private listeners: { [K in keyof T]?: Array<EventCallback<T, K>> } = {}; | ||
private listeners: { | ||
[K in keyof T]?: Record<string, ListenerPayload<T, K>>; | ||
} = {}; | ||
on<K extends keyof T>(event: K, listener: EventCallback<T, K>): void { | ||
on<K extends keyof T>( | ||
event: K, | ||
listener: ListenerPayload<T, K>, | ||
id: string, | ||
): void { | ||
if (!this.listeners[event]) { | ||
this.listeners[event] = []; | ||
this.listeners[event] = {}; | ||
} | ||
this.listeners[event]!.push(listener); | ||
(this.listeners[event] as Record<string, ListenerPayload<T, K>>)[id] = // ⬅ Workaround for TS limitation (not able to infer that this.listeners[event] is defined) | ||
listener; | ||
} | ||
off<K extends keyof T>(event: K, listener: EventCallback<T, K>): void { | ||
if (!this.listeners[event]) return; | ||
this.listeners[event] = this.listeners[event]!.filter( | ||
(l) => l !== listener, | ||
); | ||
off<K extends keyof T>(event: K, id: string): void { | ||
if (!this.listeners[event]?.[id]) return; | ||
(this.listeners[event] as Record<string, ListenerPayload<T, K>>)[ // ⬅ Workaround for TS limitation (not able to infer that this.listeners[event] is defined) | ||
id | ||
].subscription | ||
.unsubscribe(); | ||
delete (this.listeners[event] as Record<string, ListenerPayload<T, K>>)[id]; // ⬅ Workaround for TS limitation (not able to infer that this.listeners[event] is defined) | ||
} | ||
emit<K extends keyof T>(event: K, ...args: T[K]): void { | ||
if (!this.listeners[event]) return; | ||
for (const listener of this.listeners[event]!) { | ||
listener(...args); | ||
emit<K extends keyof T>(event: K, id: string, ...args: T[K]): void { | ||
if (!this.listeners[event]?.[id]) return; | ||
if (this.listeners[event]?.[id]) { | ||
(this.listeners[event] as Record<string, ListenerPayload<T, K>>)[id] // ⬅ Workaround for TS limitation (not able to infer that this.listeners[event] is defined) | ||
.callback(...args); | ||
} | ||
} | ||
} |
import { GraphQLClient } from 'graphql-request'; | ||
import { Subscription } from 'relay-runtime'; | ||
import { v4 as uuid } from 'uuid'; | ||
import { version } from '../package.json'; | ||
import { NotifiEmitterEvents } from './NotifiEventEmitter'; | ||
import { NotifiSubscriptionService } from './NotifiSubscriptionService'; | ||
import { stateChangedSubscriptionQuery } from './gql'; | ||
import * as Generated from './gql/generated'; | ||
import { getSdk } from './gql/generated'; | ||
import type * as Operations from './operations'; | ||
import { stateChangedSubscriptionQuery } from './gql'; | ||
import { NotifiEmitterEvents } from './NotifiEventEmitter'; | ||
import { Subscription } from 'relay-runtime'; | ||
@@ -456,8 +456,8 @@ export class NotifiService | ||
*/ | ||
async subscribeNotificationHistoryStateChanged( | ||
subscribeNotificationHistoryStateChanged( | ||
onMessageReceived: (data: any) => void | undefined, | ||
onError?: (data: any) => void | undefined, | ||
onComplete?: () => void | undefined, | ||
): Promise<void> { | ||
this._notifiSubService.subscribe( | ||
): Subscription | null { | ||
return this._notifiSubService.subscribe( | ||
this._jwt, | ||
@@ -475,18 +475,24 @@ stateChangedSubscriptionQuery, | ||
/** | ||
* @returns {string} - The id of the event listener (used to remove the event listener) | ||
*/ | ||
addEventListener<T extends keyof NotifiEmitterEvents>( | ||
event: T, | ||
callBack: (...args: NotifiEmitterEvents[T]) => void, | ||
): Subscription | null { | ||
return this._notifiSubService.addEventListener(event, callBack); | ||
onError?: (error: unknown) => void, | ||
onComplete?: () => void, | ||
): string { | ||
return this._notifiSubService.addEventListener( | ||
event, | ||
callBack, | ||
onError, | ||
onComplete, | ||
); | ||
} | ||
/** | ||
* @important To remove event listener, check the README.md of `notifi-node` or `notifi-frontend-client` package for more details. | ||
* - `notifi-node`: https://github.com/notifi-network/notifi-sdk-ts/tree/main/packages/notifi-node | ||
* - `notifi-frontend-client`: https://github.com/notifi-network/notifi-sdk-ts/tree/main/packages/notifi-frontend-client | ||
*/ | ||
removeEventListener<T extends keyof NotifiEmitterEvents>( | ||
event: T, | ||
callBack: (...args: NotifiEmitterEvents[T]) => void, | ||
id: string, | ||
) { | ||
return this._notifiSubService.removeEventListener(event, callBack); | ||
return this._notifiSubService.removeEventListener(event, id); | ||
} | ||
@@ -493,0 +499,0 @@ |
import { | ||
SubscribePayload, | ||
Client as WebSocketClient, | ||
createClient, | ||
Client as WebSocketClient, | ||
SubscribePayload, | ||
} from 'graphql-ws'; | ||
import { Observable, Subscription } from 'relay-runtime'; | ||
import { | ||
NotifiEmitterEvents, | ||
NotifiEventEmitter, | ||
NotifiEmitterEvents, | ||
NotifiSubscriptionEvents, | ||
@@ -15,6 +16,15 @@ } from './NotifiEventEmitter'; | ||
type SubscriptionQuery = | ||
export type SubscriptionQuery = | ||
| typeof stateChangedSubscriptionQuery | ||
| typeof tenantActiveAlertChangedSubscriptionQuery; | ||
export type SubscribeInputs = { | ||
subscriptionQuery: SubscriptionQuery; | ||
id: string; | ||
onError?: (error: unknown) => void; | ||
onComplete?: () => void; | ||
}; | ||
type _Subscribe = (input: SubscribeInputs) => Subscription | null; | ||
/** | ||
@@ -38,84 +48,46 @@ * @param webSocketImpl - A custom WebSocket implementation to use instead of the one provided by the global scope. Mostly useful for when using the client outside of the browser environment. | ||
/** | ||
* @deprecated Should not directly manipulate the websocket client. Instead use the returned subscription object to manage the subscription. ex. subscription.unsubscribe() | ||
* @returns {string} - The id of the event listener (used to remove the event listener) | ||
*/ | ||
disposeClient = () => { | ||
if (this._wsClient) { | ||
this._jwt = undefined; | ||
this._wsClient.terminate(); | ||
this._wsClient.dispose(); | ||
} | ||
}; | ||
/** | ||
* @deprecated Use addEventListener instead | ||
*/ | ||
subscribe = ( | ||
jwt: string | undefined, | ||
subscriptionQuery: string, | ||
onMessageReceived: (data: any) => void | undefined, | ||
onError?: (data: any) => void | undefined, | ||
onComplete?: () => void | undefined, | ||
) => { | ||
this._jwt = jwt; | ||
if (!this._wsClient) { | ||
this._initializeClient(); | ||
} | ||
if (!this._wsClient) return null; | ||
const observable = this._toObservable(this._wsClient, { | ||
query: subscriptionQuery, | ||
extensions: { | ||
type: 'start', | ||
}, | ||
}); | ||
const subscription = observable.subscribe({ | ||
next: (data) => { | ||
if (onMessageReceived) { | ||
onMessageReceived(data); | ||
} | ||
}, | ||
error: (error: unknown) => { | ||
if (onError && error instanceof Error) { | ||
onError(error); | ||
} | ||
}, | ||
complete: () => { | ||
console.log('Subscription complete'); | ||
if (onComplete) { | ||
onComplete(); | ||
} | ||
}, | ||
}); | ||
return subscription; | ||
}; | ||
/** | ||
* @important for removing the event listener, check the guidelines in the NotifiEventEmitter (notifi-graphql/lib/NotifiEventEmitter.ts) class. https://github.com/notifi-network/notifi-sdk-ts/tree/main/packages/notifi-graphql/lib | ||
*/ | ||
addEventListener = <T extends keyof NotifiEmitterEvents>( | ||
event: T, | ||
callBack: (...args: NotifiEmitterEvents[T]) => void, | ||
) => { | ||
this.eventEmitter.on(event, callBack); | ||
callback: (...args: NotifiEmitterEvents[T]) => void, | ||
onError?: (error: unknown) => void, | ||
onComplete?: () => void, | ||
): string => { | ||
const id = Math.random().toString(36).slice(2, 11); // ⬅ Generate a random id for the listener | ||
const subscribeInputs: SubscribeInputs = { | ||
subscriptionQuery: '' as SubscriptionQuery, // ⬅ Placeholder (empty string intentionally) | ||
id, | ||
onError, | ||
onComplete, | ||
}; | ||
switch (event) { | ||
case 'stateChanged': | ||
return this._subscribe(stateChangedSubscriptionQuery); | ||
subscribeInputs.subscriptionQuery = stateChangedSubscriptionQuery; | ||
break; | ||
case 'tenantActiveAlertChanged': | ||
return this._subscribe(tenantActiveAlertChangedSubscriptionQuery); | ||
subscribeInputs.subscriptionQuery = | ||
tenantActiveAlertChangedSubscriptionQuery; | ||
break; | ||
default: | ||
return null; | ||
throw new Error('Unknown event'); | ||
} | ||
const subscription = this._subscribe(subscribeInputs); | ||
if (!subscription) | ||
throw new Error( | ||
'NotifiSubscriptionService.addEventListener: Subscription failed', | ||
); | ||
this.eventEmitter.on(event, { callback, subscription }, id); | ||
return id; | ||
}; | ||
/** | ||
* @important To remove event listener, check the README.md of `notifi-node` or `notifi-frontend-client` package for more details. | ||
* - `notifi-node`: https://github.com/notifi-network/notifi-sdk-ts/tree/main/packages/notifi-node | ||
* - `notifi-frontend-client`: https://github.com/notifi-network/notifi-sdk-ts/tree/main/packages/notifi-frontend-client | ||
*/ | ||
removeEventListener = <T extends keyof NotifiEmitterEvents>( | ||
event: T, | ||
callBack: (...args: NotifiEmitterEvents[T]) => void, | ||
id: string, | ||
) => { | ||
return this.eventEmitter.off(event, callBack); | ||
return this.eventEmitter.off(event, id); | ||
}; | ||
@@ -126,5 +98,8 @@ | ||
*/ | ||
private _subscribe = ( | ||
subscriptionQuery: SubscriptionQuery, | ||
): Subscription | null => { | ||
private _subscribe: _Subscribe = ({ | ||
subscriptionQuery, | ||
id, | ||
onError, | ||
onComplete, | ||
}) => { | ||
if (!this._wsClient) { | ||
@@ -136,4 +111,2 @@ this._initializeClient(); | ||
console.log('Subscribing, JWT & wsClient are set'); // TODO: Remove before merge | ||
const observable = this._toObservable(this._wsClient, { | ||
@@ -154,3 +127,3 @@ query: subscriptionQuery, | ||
} | ||
this.eventEmitter.emit('stateChanged', stateChangedData); | ||
this.eventEmitter.emit('stateChanged', id, stateChangedData); | ||
break; | ||
@@ -167,2 +140,3 @@ case tenantActiveAlertChangedSubscriptionQuery: | ||
'tenantActiveAlertChanged', | ||
id, | ||
tenantActiveAlertChangedData, | ||
@@ -175,11 +149,4 @@ ); | ||
}, | ||
error: (error: unknown) => { | ||
this.eventEmitter.emit( | ||
'gqlSubscriptionError', | ||
error instanceof Error | ||
? error | ||
: new Error('Unknown gql subscription error'), | ||
); | ||
}, | ||
complete: () => this.eventEmitter.emit('gqlComplete'), | ||
error: onError, | ||
complete: onComplete, | ||
}); | ||
@@ -205,2 +172,7 @@ | ||
private _initializeClient = () => { | ||
if (!this._jwt) | ||
throw new Error( | ||
'NotifiSubscriptionService._initializeClient: Missing JWT', | ||
); | ||
this._wsClient = createClient({ | ||
@@ -215,23 +187,85 @@ url: this.wsurl, | ||
this._wsClient.on('connecting', () => { | ||
this.eventEmitter.emit('wsConnecting'); | ||
}); | ||
/** ⬇ Uncomment to monitor websocket behavior (debugging purpose) */ | ||
// this._wsClient.on('connecting', () => { | ||
// console.info( | ||
// 'NotifiSubscriptionService._initializeClient: Connecting to ws', | ||
// ); | ||
// }); | ||
this._wsClient.on('connected', () => { | ||
this.eventEmitter.emit('wsConnected', this._wsClient!); | ||
// this._wsClient.on('connected', () => { | ||
// console.info( | ||
// 'NotifiSubscriptionService._initializeClient: Connected to ws', | ||
// ); | ||
// }); | ||
// this._wsClient.on('closed', (event) => { | ||
// console.info( | ||
// 'NotifiSubscriptionService._initializeClient: Closed ws', | ||
// event, | ||
// ); | ||
// }); | ||
// this._wsClient.on('error', (error) => { | ||
// console.error( | ||
// 'NotifiSubscriptionService._initializeClient: Websocket Error:', | ||
// error, | ||
// ); | ||
// }); | ||
}; | ||
/* ⬇⬇⬇⬇⬇ Deprecated methods ⬇⬇⬇⬇⬇⬇ */ | ||
/** | ||
* @deprecated Should not directly manipulate the websocket client. Instead use the returned subscription object to manage the subscription. ex. subscription.unsubscribe() | ||
*/ | ||
disposeClient = () => { | ||
if (this._wsClient) { | ||
this._jwt = undefined; | ||
this._wsClient.terminate(); | ||
this._wsClient.dispose(); | ||
} | ||
}; | ||
/** | ||
* @deprecated Use addEventListener instead | ||
*/ | ||
subscribe = ( | ||
jwt: string | undefined, | ||
subscriptionQuery: string, | ||
onMessageReceived: (data: any) => void | undefined, | ||
onError?: (data: any) => void | undefined, | ||
onComplete?: () => void | undefined, | ||
) => { | ||
this._jwt = jwt; | ||
if (!this._wsClient) { | ||
this._initializeClient(); | ||
} | ||
if (!this._wsClient) return null; | ||
const observable = this._toObservable(this._wsClient, { | ||
query: subscriptionQuery, | ||
extensions: { | ||
type: 'start', | ||
}, | ||
}); | ||
this._wsClient.on('closed', (event) => { | ||
this.eventEmitter.emit('wsClosed', event); | ||
const subscription = observable.subscribe({ | ||
next: (data) => { | ||
if (onMessageReceived) { | ||
onMessageReceived(data); | ||
} | ||
}, | ||
error: (error: unknown) => { | ||
if (onError && error instanceof Error) { | ||
onError(error); | ||
} | ||
}, | ||
complete: () => { | ||
if (onComplete) { | ||
onComplete(); | ||
} | ||
}, | ||
}); | ||
this._wsClient.on('error', (error) => { | ||
if (error instanceof Error /*⬅ Client (browser) side error*/) { | ||
return this.eventEmitter.emit('wsError', error); | ||
} | ||
this.eventEmitter.emit('wsError', { | ||
...(error as Error), | ||
message: 'NotifiEventEmitter: Server side or unknown error', | ||
}); | ||
}); | ||
return subscription; | ||
}; | ||
@@ -238,0 +272,0 @@ } |
{ | ||
"name": "@notifi-network/notifi-graphql", | ||
"version": "3.0.2-alpha.11+1cedd3be", | ||
"version": "3.1.0", | ||
"description": "The GraphQL API for Notifi", | ||
@@ -49,3 +49,3 @@ "main": "./dist/index.js", | ||
}, | ||
"gitHead": "1cedd3be1f08ba8f9e318b65ce9d8b6a36f02107" | ||
"gitHead": "23052e48ac17f22c51635ea827d6f1a107352ec5" | ||
} |
{ | ||
"extends": "../../tsconfig.json", | ||
} |
@@ -0,0 +0,0 @@ { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
18055
1
0
1332786