From 7de9d5dc9dca9b05f3829f729f3d2ebba69e593c Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Tue, 12 Nov 2024 11:36:22 -0500 Subject: [PATCH 1/9] feat: Add notification token callback to use en addMessage Method --- docs/message-pickup-repository-client.md | 15 ++++- .../src/MessagePickupRepositoryClient.ts | 67 ++++++++++++++----- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/docs/message-pickup-repository-client.md b/docs/message-pickup-repository-client.md index 79f1810..13c4a6f 100644 --- a/docs/message-pickup-repository-client.md +++ b/docs/message-pickup-repository-client.md @@ -32,7 +32,7 @@ npm install rpc-websockets @nestjs/common ## Available Methods -### `connect()` +### `connect(options:{ agent })` Establishes a WebSocket connection to the server. @@ -54,6 +54,19 @@ Registers a callback to handle `messagesReceived` events from the WebSocket serv --- +### `notificationToken(callback)` + +Registers a callback to retrieve the `device_token` associated with a specific connection from mediator. This method is primarily used to provide the token when sending messages to the WebSocket server. + +- **Parameters**: + + - `callback`: A function that receives a `connectionId` as a parameter and returns a `Promise` that resolves to the device token or `undefined` if no token is available. + - `connectionId: string`: The connection ID for which to retrieve the token. + +- **Returns**: `void` + +--- + ### `takeFromQueue(params)` Retrieves messages from the queue. diff --git a/packages/client/src/MessagePickupRepositoryClient.ts b/packages/client/src/MessagePickupRepositoryClient.ts index 63d8d58..0a5bcf4 100644 --- a/packages/client/src/MessagePickupRepositoryClient.ts +++ b/packages/client/src/MessagePickupRepositoryClient.ts @@ -21,6 +21,7 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { private client?: Client private readonly logger = log private messagesReceivedCallback: ((data: MessagesReceivedCallbackParams) => void) | null = null + private getDeviceTokenCallback?: (connectionId: string) => Promise private readonly url: string private readonly maxReceiveBytes?: number @@ -62,6 +63,14 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { }) } + /** + * Checks if the WebSocket client is initialized and returns it. + * If the client is not initialized, throws an error instructing to call `connect()` first. + * + * @private + * @throws {Error} Will throw an error if the client is not initialized. + * @returns {Client} The initialized WebSocket client instance. + */ private checkClient(): Client { if (!this.client) { throw new Error('Client is not initialized. Call connect() first.') @@ -94,6 +103,27 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { this.messagesReceivedCallback = callback } + /** + * Sets the callback function to retrieve the device token. + * This function is called whenever a token is needed, allowing dynamic retrieval based on the connection ID. + * + * @param callback - A function that receives a connectionId and returns a Promise resolving to a device token or undefined. + * + * @example + * // Example of setting the callback to retrieve the device token + * const client = new MessagePickupRepositoryClient({ url: 'wss://example.com' }); + * + * const getDeviceToken = async (connectionId: string) => { + * const connectionRecord = await agent.connections.findById(connectionId); + * return connectionRecord?.getTag('device_token') as string | undefined; + * }; + * + * client.notificationToken(getDeviceToken); + */ + notificationToken(callback: (connectionId: string) => Promise): void { + this.getDeviceTokenCallback = callback + } + /** * Calls the 'takeFromQueue' RPC method on the WebSocket server. * This method sends a request to retrieve messages from the queue for the specified connection. @@ -164,33 +194,38 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { } /** - * Call the 'addMessage' RPC method. - * This method sends a request to the WebSocket server to add a message to the queue. - * It expects the response to be a string or null. + * Calls the 'addMessage' RPC method. + * This function sends a request to the WebSocket server to add a message to the queue. + * It retrieves the device token (if available) and includes it in the parameters. + * Expects the response from the server to be a JSON string or an empty string if null. * - * @param {AddMessageOptions} params - The parameters to pass to the 'addMessage' method, including: + * @param {AddMessageOptions} params - Parameters for the 'addMessage' method, including: * @property {string} connectionId - The ID of the connection to which the message will be added. - * @property {string[]} recipientDids - An array of DIDs of the recipients for whom the message is intended. + * @property {string[]} recipientDids - Array of DIDs for the intended recipients of the message. * @property {EncryptedMessage} payload - The encrypted message content to be queued. - * @returns {Promise} - The result from the WebSocket server, expected to be a string or null. - * @throws Will throw an error if the result is not an object, null, or if there's any issue with the WebSocket call. + * @property {string} [token] - (Optional) A token associated with the device; will be populated if available. + * @returns {Promise} - The server response, expected as a JSON string or an empty string if the response is null. + * @throws {Error} Will throw an error if the result is neither an object nor null, or if any issue occurs during the WebSocket call. */ async addMessage(params: AddMessageOptions): Promise { try { const client = this.checkClient() - // Call the RPC method and store the result as 'unknown' type initially + + // Retrieve token using the callback, if set + params.token = this.getDeviceTokenCallback ? await this.getDeviceTokenCallback(params.connectionId) : undefined + + // Call the 'addMessage' RPC method on the WebSocket server const result: unknown = await client.call('addMessage', params, 2000) + // Log the result and handle the response format this.logger.debug(`**** result: ${JSON.stringify(result, null, 2)} ***`) - // Check if the result is a string and cast it - if (result && typeof result === 'object') { - return JSON.stringify(result) - } else if (result === null) { - return '' - } else { - throw new Error('Unexpected result: Expected an object or null') - } + // Return JSON stringified result if it's an object, or an empty string if result is null + if (result === null) return '' + if (typeof result === 'object') return JSON.stringify(result) + + // If result is neither an object nor null, throw an error + throw new Error('Unexpected result: Expected an object or null') } catch (error) { // Log the error and rethrow it for further handling this.logger.error('Error calling addMessage:', error) From 97e8f9587f94c85edb05d8467b0a6231fb7f86d6 Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Tue, 12 Nov 2024 11:47:22 -0500 Subject: [PATCH 2/9] fix: fix documentation MPR client connect method --- docs/message-pickup-repository-client.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/message-pickup-repository-client.md b/docs/message-pickup-repository-client.md index 13c4a6f..ebb6cd8 100644 --- a/docs/message-pickup-repository-client.md +++ b/docs/message-pickup-repository-client.md @@ -32,7 +32,7 @@ npm install rpc-websockets @nestjs/common ## Available Methods -### `connect(options:{ agent })` +### `connect()` Establishes a WebSocket connection to the server. From c45ddd114bb80c4a67a30867fd48b2ea328144ae Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Tue, 12 Nov 2024 11:59:54 -0500 Subject: [PATCH 3/9] feat: add ExtendedAddMessageOptions interface to handle fcm token --- packages/client/src/MessagePickupRepositoryClient.ts | 5 +++-- packages/client/src/interfaces.ts | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/client/src/MessagePickupRepositoryClient.ts b/packages/client/src/MessagePickupRepositoryClient.ts index 0a5bcf4..397cddb 100644 --- a/packages/client/src/MessagePickupRepositoryClient.ts +++ b/packages/client/src/MessagePickupRepositoryClient.ts @@ -6,6 +6,7 @@ import { AddLiveSessionOptions, MessagesReceivedCallbackParams, ExtendedTakeFromQueueOptions, + ExtendedAddMessageOptions, } from './interfaces' import { AddMessageOptions, @@ -199,7 +200,7 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { * It retrieves the device token (if available) and includes it in the parameters. * Expects the response from the server to be a JSON string or an empty string if null. * - * @param {AddMessageOptions} params - Parameters for the 'addMessage' method, including: + * @param {ExtendedAddMessageOptions} params - Parameters for the 'addMessage' method, including: * @property {string} connectionId - The ID of the connection to which the message will be added. * @property {string[]} recipientDids - Array of DIDs for the intended recipients of the message. * @property {EncryptedMessage} payload - The encrypted message content to be queued. @@ -207,7 +208,7 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { * @returns {Promise} - The server response, expected as a JSON string or an empty string if the response is null. * @throws {Error} Will throw an error if the result is neither an object nor null, or if any issue occurs during the WebSocket call. */ - async addMessage(params: AddMessageOptions): Promise { + async addMessage(params: ExtendedAddMessageOptions): Promise { try { const client = this.checkClient() diff --git a/packages/client/src/interfaces.ts b/packages/client/src/interfaces.ts index 95ef12f..4587dce 100644 --- a/packages/client/src/interfaces.ts +++ b/packages/client/src/interfaces.ts @@ -1,4 +1,4 @@ -import { QueuedMessage, TakeFromQueueOptions } from '@credo-ts/core' +import { AddMessageOptions, QueuedMessage, TakeFromQueueOptions } from '@credo-ts/core' export interface RemoveAllMessagesOptions { connectionId: string @@ -22,3 +22,7 @@ export interface MessagesReceivedCallbackParams { export interface ExtendedTakeFromQueueOptions extends TakeFromQueueOptions { limitBytes?: number } + +export interface ExtendedAddMessageOptions extends AddMessageOptions { + token?: string +} From 9a06b735fff63eb70e9ded96a3f383f57d3f23dd Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Wed, 13 Nov 2024 12:02:38 -0500 Subject: [PATCH 4/9] feat: add generic setConnectionInfo callback for dynamic connection data retrieval --- docs/message-pickup-repository-client.md | 10 ++-- .../src/MessagePickupRepositoryClient.ts | 50 ++++++++++++------- packages/client/src/interfaces.ts | 5 ++ 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/docs/message-pickup-repository-client.md b/docs/message-pickup-repository-client.md index ebb6cd8..9732655 100644 --- a/docs/message-pickup-repository-client.md +++ b/docs/message-pickup-repository-client.md @@ -54,16 +54,16 @@ Registers a callback to handle `messagesReceived` events from the WebSocket serv --- -### `notificationToken(callback)` +### `setConnectionInfo(callback)` -Registers a callback to retrieve the `device_token` associated with a specific connection from mediator. This method is primarily used to provide the token when sending messages to the WebSocket server. +Registers a callback function to retrieve connection-specific information based on a given `connectionId`. This callback provides the `ConnectionInfo` object, which contains details such as the FCM notification token and maximum bytes allowed for receiving messages. This function is useful for dynamically fetching connection-related information whenever it is required by the client. - **Parameters**: - - `callback`: A function that receives a `connectionId` as a parameter and returns a `Promise` that resolves to the device token or `undefined` if no token is available. - - `connectionId: string`: The connection ID for which to retrieve the token. + - `callback`: A function that takes a `connectionId` (string) and returns a `Promise` resolving to a `ConnectionInfo` object or `undefined` if no information is available for the given `connectionId`. + - `connectionId` (string): The ID of the connection for which to retrieve information. -- **Returns**: `void` +- **Returns**: `Promise`: The connection information, including: - `fcmNotificationToken` (optional, string): The FCM notification token for the specified connection. - `maxReceiveBytes` (optional, number): The maximum allowed bytes for receiving messages for this connection. --- diff --git a/packages/client/src/MessagePickupRepositoryClient.ts b/packages/client/src/MessagePickupRepositoryClient.ts index 397cddb..82ff3ab 100644 --- a/packages/client/src/MessagePickupRepositoryClient.ts +++ b/packages/client/src/MessagePickupRepositoryClient.ts @@ -7,6 +7,7 @@ import { MessagesReceivedCallbackParams, ExtendedTakeFromQueueOptions, ExtendedAddMessageOptions, + ConnectionInfo, } from './interfaces' import { AddMessageOptions, @@ -22,13 +23,12 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { private client?: Client private readonly logger = log private messagesReceivedCallback: ((data: MessagesReceivedCallbackParams) => void) | null = null - private getDeviceTokenCallback?: (connectionId: string) => Promise + private setConnectionInfoCallback?: (connectionId: string) => Promise private readonly url: string - private readonly maxReceiveBytes?: number + - constructor(options: { url: string; maxReceiveBytes?: number }) { + constructor(options: { url: string }) { this.url = options.url - this.maxReceiveBytes = options.maxReceiveBytes } /** @@ -105,24 +105,29 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { } /** - * Sets the callback function to retrieve the device token. - * This function is called whenever a token is needed, allowing dynamic retrieval based on the connection ID. + * Sets the callback function to retrieve connection-specific information. + * This callback provides the `ConnectionInfo` object, containing details like + * the FCM notification token and max receive bytes, based on the given `connectionId`. * - * @param callback - A function that receives a connectionId and returns a Promise resolving to a device token or undefined. + * @param {function} callback - A function that takes a `connectionId` as a parameter and returns + * a `Promise` that resolves to a `ConnectionInfo` object or `undefined` if no information is available. * * @example - * // Example of setting the callback to retrieve the device token + * // Example of setting the callback to retrieve connection-specific information * const client = new MessagePickupRepositoryClient({ url: 'wss://example.com' }); * - * const getDeviceToken = async (connectionId: string) => { + * const getConnectionInfo = async (connectionId: string) => { * const connectionRecord = await agent.connections.findById(connectionId); - * return connectionRecord?.getTag('device_token') as string | undefined; + * return { + * fcmNotificationToken: connectionRecord?.getTag('device_token') as string | undefined, + * maxReceiveBytes: config.messagePickupMaxReceiveBytes, + * }; * }; * - * client.notificationToken(getDeviceToken); + * client.setConnectionInfo(getConnectionInfo); */ - notificationToken(callback: (connectionId: string) => Promise): void { - this.getDeviceTokenCallback = callback + setConnectionInfo(callback: (connectionId: string) => Promise): void { + this.setConnectionInfoCallback = callback } /** @@ -147,9 +152,15 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { try { const client = this.checkClient() + const connectionInfo = this.setConnectionInfoCallback + ? await this.setConnectionInfoCallback(params.connectionId) + : undefined + + const maxReceiveBytes = connectionInfo?.maxReceiveBytes + // Add limitBytes to params if maxReceiveBytes is set - if (this.maxReceiveBytes) { - params = { ...params, limitBytes: this.maxReceiveBytes } + if (maxReceiveBytes) { + params = { ...params, limitBytes: maxReceiveBytes } } // Call the RPC method and store the result as 'unknown' type initially @@ -212,8 +223,13 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { try { const client = this.checkClient() - // Retrieve token using the callback, if set - params.token = this.getDeviceTokenCallback ? await this.getDeviceTokenCallback(params.connectionId) : undefined + // Retrieve connection information using the callback, if set + const connectionInfo = this.setConnectionInfoCallback + ? await this.setConnectionInfoCallback(params.connectionId) + : undefined + + // Set the token and max bytes from the connection info, if available + params.token = connectionInfo?.fcmNotificationToken // Call the 'addMessage' RPC method on the WebSocket server const result: unknown = await client.call('addMessage', params, 2000) diff --git a/packages/client/src/interfaces.ts b/packages/client/src/interfaces.ts index 4587dce..3a9d572 100644 --- a/packages/client/src/interfaces.ts +++ b/packages/client/src/interfaces.ts @@ -26,3 +26,8 @@ export interface ExtendedTakeFromQueueOptions extends TakeFromQueueOptions { export interface ExtendedAddMessageOptions extends AddMessageOptions { token?: string } + +export interface ConnectionInfo { + fcmNotificationToken?: string + maxReceiveBytes?: number +} From 58b0a2e377ed0a1dcb6dfda016a4d8927e71f247 Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Wed, 13 Nov 2024 12:26:20 -0500 Subject: [PATCH 5/9] fix: rename variable connectionInfo to connectionInfoCallback --- packages/client/src/MessagePickupRepositoryClient.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/client/src/MessagePickupRepositoryClient.ts b/packages/client/src/MessagePickupRepositoryClient.ts index 82ff3ab..f7f8662 100644 --- a/packages/client/src/MessagePickupRepositoryClient.ts +++ b/packages/client/src/MessagePickupRepositoryClient.ts @@ -25,7 +25,6 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { private messagesReceivedCallback: ((data: MessagesReceivedCallbackParams) => void) | null = null private setConnectionInfoCallback?: (connectionId: string) => Promise private readonly url: string - constructor(options: { url: string }) { this.url = options.url @@ -152,11 +151,11 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { try { const client = this.checkClient() - const connectionInfo = this.setConnectionInfoCallback + const connectionInfoCallback = this.setConnectionInfoCallback ? await this.setConnectionInfoCallback(params.connectionId) : undefined - const maxReceiveBytes = connectionInfo?.maxReceiveBytes + const maxReceiveBytes = connectionInfoCallback?.maxReceiveBytes // Add limitBytes to params if maxReceiveBytes is set if (maxReceiveBytes) { @@ -224,12 +223,12 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { const client = this.checkClient() // Retrieve connection information using the callback, if set - const connectionInfo = this.setConnectionInfoCallback + const connectionInfoCallback = this.setConnectionInfoCallback ? await this.setConnectionInfoCallback(params.connectionId) : undefined // Set the token and max bytes from the connection info, if available - params.token = connectionInfo?.fcmNotificationToken + params.token = connectionInfoCallback?.fcmNotificationToken // Call the 'addMessage' RPC method on the WebSocket server const result: unknown = await client.call('addMessage', params, 2000) From 3182577ca9cdccef81a9db0b13afa7aae1d01317 Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Wed, 13 Nov 2024 15:10:13 -0500 Subject: [PATCH 6/9] Update packages/client/src/MessagePickupRepositoryClient.ts Co-authored-by: Ariel Gentile --- packages/client/src/MessagePickupRepositoryClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/MessagePickupRepositoryClient.ts b/packages/client/src/MessagePickupRepositoryClient.ts index f7f8662..553ee69 100644 --- a/packages/client/src/MessagePickupRepositoryClient.ts +++ b/packages/client/src/MessagePickupRepositoryClient.ts @@ -23,7 +23,7 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { private client?: Client private readonly logger = log private messagesReceivedCallback: ((data: MessagesReceivedCallbackParams) => void) | null = null - private setConnectionInfoCallback?: (connectionId: string) => Promise + private connectionInfoCallback?: (connectionId: string) => Promise private readonly url: string constructor(options: { url: string }) { From 328f9522ec77ceec3f579e3bcf49720c7cb34884 Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Wed, 13 Nov 2024 15:10:23 -0500 Subject: [PATCH 7/9] Update packages/client/src/MessagePickupRepositoryClient.ts Co-authored-by: Ariel Gentile --- packages/client/src/MessagePickupRepositoryClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/MessagePickupRepositoryClient.ts b/packages/client/src/MessagePickupRepositoryClient.ts index 553ee69..d3c0efd 100644 --- a/packages/client/src/MessagePickupRepositoryClient.ts +++ b/packages/client/src/MessagePickupRepositoryClient.ts @@ -151,11 +151,11 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { try { const client = this.checkClient() - const connectionInfoCallback = this.setConnectionInfoCallback - ? await this.setConnectionInfoCallback(params.connectionId) + const connectionInfo = this.connectionInfoCallback + ? await this.connectionInfoCallback(params.connectionId) : undefined - const maxReceiveBytes = connectionInfoCallback?.maxReceiveBytes + const maxReceiveBytes = connectionInfo?.maxReceiveBytes // Add limitBytes to params if maxReceiveBytes is set if (maxReceiveBytes) { From 1c227d3f5ab62657d086a8522a82d1e2de18be41 Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Wed, 13 Nov 2024 15:10:39 -0500 Subject: [PATCH 8/9] Update packages/client/src/MessagePickupRepositoryClient.ts Co-authored-by: Ariel Gentile --- packages/client/src/MessagePickupRepositoryClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/MessagePickupRepositoryClient.ts b/packages/client/src/MessagePickupRepositoryClient.ts index d3c0efd..5055b65 100644 --- a/packages/client/src/MessagePickupRepositoryClient.ts +++ b/packages/client/src/MessagePickupRepositoryClient.ts @@ -223,12 +223,12 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { const client = this.checkClient() // Retrieve connection information using the callback, if set - const connectionInfoCallback = this.setConnectionInfoCallback - ? await this.setConnectionInfoCallback(params.connectionId) + const connectionInfo = this.connectionInfoCallback + ? await this.connectionInfoCallback(params.connectionId) : undefined // Set the token and max bytes from the connection info, if available - params.token = connectionInfoCallback?.fcmNotificationToken + params.token = connectionInfo?.fcmNotificationToken // Call the 'addMessage' RPC method on the WebSocket server const result: unknown = await client.call('addMessage', params, 2000) From 86d411f059dcd8fd5018cd81e10fdfd307d5dd9d Mon Sep 17 00:00:00 2001 From: Gabriel Mata Date: Wed, 13 Nov 2024 15:16:03 -0500 Subject: [PATCH 9/9] fix: fix callback name connectionInfoCallback into setConnectionInfo --- packages/client/src/MessagePickupRepositoryClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/MessagePickupRepositoryClient.ts b/packages/client/src/MessagePickupRepositoryClient.ts index 5055b65..3d9b829 100644 --- a/packages/client/src/MessagePickupRepositoryClient.ts +++ b/packages/client/src/MessagePickupRepositoryClient.ts @@ -126,7 +126,7 @@ export class MessagePickupRepositoryClient implements MessagePickupRepository { * client.setConnectionInfo(getConnectionInfo); */ setConnectionInfo(callback: (connectionId: string) => Promise): void { - this.setConnectionInfoCallback = callback + this.connectionInfoCallback = callback } /**