Skip to content

Commit f09a951

Browse files
committed
feat(messaging-groups): wire optional recovery transport into client with recoverMessages()
1 parent 949f1ea commit f09a951

4 files changed

Lines changed: 136 additions & 11 deletions

File tree

ts-sdks/packages/messaging-groups/src/client.ts

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ import type {
3333
GetMessageOptions,
3434
GetMessagesOptions,
3535
GetMessagesResult,
36+
RecoverMessagesOptions,
3637
SendMessageOptions,
3738
SubscribeOptions,
3839
} from './messaging-types.js';
40+
import type { RecoveryTransport } from './recovery/transport.js';
3941
import type {
4042
ArchiveGroupOptions,
4143
CreateGroupOptions,
@@ -91,6 +93,7 @@ export function messagingGroups<
9193
suinsConfig,
9294
relayer,
9395
attachments,
96+
recovery,
9497
}: {
9598
name?: Name;
9699
/** Name under which the PermissionedGroupsClient extension is registered (default: 'groups'). */
@@ -105,6 +108,8 @@ export function messagingGroups<
105108
relayer: RelayerConfig;
106109
/** Attachment support. When omitted, messages cannot include files. */
107110
attachments?: MessagingGroupsClientOptions<TApproveContext>['attachments'];
111+
/** Optional recovery transport for fetching messages from an alternative storage backend. */
112+
recovery?: RecoveryTransport;
108113
}) {
109114
return {
110115
name,
@@ -118,6 +123,7 @@ export function messagingGroups<
118123
encryption,
119124
relayer,
120125
attachments,
126+
recovery,
121127
});
122128
},
123129
};
@@ -157,6 +163,7 @@ export class MessagingGroupsClient<TApproveContext = void> {
157163
#packageConfig: MessagingGroupsPackageConfig;
158164
#client: ClientWithCoreApi;
159165
#attachments: AttachmentsManager<TApproveContext> | undefined;
166+
#recovery: RecoveryTransport | undefined;
160167
readonly #textEncoder = new TextEncoder();
161168
readonly #textDecoder = new TextDecoder();
162169

@@ -247,6 +254,8 @@ export class MessagingGroupsClient<TApproveContext = void> {
247254
timeout: options.relayer.timeout,
248255
onError: options.relayer.onError,
249256
});
257+
258+
this.#recovery = options.recovery;
250259
}
251260

252261
// === Private Helpers ===
@@ -523,6 +532,51 @@ export class MessagingGroupsClient<TApproveContext = void> {
523532
this.transport.disconnect();
524533
}
525534

535+
// === Recovery ===
536+
537+
/**
538+
* Fetch and decrypt messages from the recovery transport.
539+
*
540+
* Requires a `recovery` transport to be configured at client creation.
541+
* Recovery is read-only and does not require a signer.
542+
*
543+
* @throws {MessagingGroupsClientError} if no recovery transport is configured.
544+
*/
545+
async recoverMessages(
546+
options: RecoverMessagesOptions<TApproveContext>,
547+
): Promise<GetMessagesResult> {
548+
if (!this.#recovery) {
549+
throw new MessagingGroupsClientError(
550+
'Recovery transport is not configured. Provide `recovery` when creating the messaging groups client.',
551+
);
552+
}
553+
554+
const { groupId, encryptionHistoryId } = this.derive.resolveGroupRef(options.groupRef);
555+
const approveContext = this.#approveContextSpread(options);
556+
557+
const result = await this.#recovery.recoverMessages({
558+
groupId,
559+
afterOrder: options.afterOrder,
560+
beforeOrder: options.beforeOrder,
561+
limit: options.limit,
562+
});
563+
564+
const settled = await Promise.allSettled(
565+
result.messages.map((raw) =>
566+
this.#decryptMessage(raw, { groupId, encryptionHistoryId }, approveContext),
567+
),
568+
);
569+
570+
const messages: DecryptedMessage[] = [];
571+
for (const entry of settled) {
572+
if (entry.status === 'fulfilled') {
573+
messages.push(entry.value);
574+
}
575+
}
576+
577+
return { messages, hasNext: result.hasNext };
578+
}
579+
526580
// === Private: sealApproveContext ===
527581

528582
/**
@@ -693,7 +747,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
693747
* Returns a tuple of (PermissionedGroup<Messaging>, EncryptionHistory).
694748
* The objects are NOT shared - use createAndShareGroup for shared groups.
695749
*/
696-
async createGroup({ signer, transaction, ...callOptions }: CreateGroupOptions & { transaction?: Transaction }) {
750+
async createGroup({
751+
signer,
752+
transaction,
753+
...callOptions
754+
}: CreateGroupOptions & { transaction?: Transaction }) {
697755
return this.#executeTransaction(
698756
this.tx.createGroup({ transaction, ...callOptions }),
699757
signer,
@@ -705,7 +763,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
705763
* Creates a new messaging group and shares both objects.
706764
* The transaction sender automatically becomes the creator with all permissions.
707765
*/
708-
async createAndShareGroup({ signer, transaction, ...callOptions }: CreateGroupOptions & { transaction?: Transaction }) {
766+
async createAndShareGroup({
767+
signer,
768+
transaction,
769+
...callOptions
770+
}: CreateGroupOptions & { transaction?: Transaction }) {
709771
return this.#executeTransaction(
710772
this.tx.createAndShareGroup({ transaction, ...callOptions }),
711773
signer,
@@ -717,7 +779,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
717779
* Rotates the encryption key for a group.
718780
* Requires EncryptionKeyRotator permission.
719781
*/
720-
async rotateEncryptionKey({ signer, transaction, ...callOptions }: RotateEncryptionKeyOptions & { transaction?: Transaction }) {
782+
async rotateEncryptionKey({
783+
signer,
784+
transaction,
785+
...callOptions
786+
}: RotateEncryptionKeyOptions & { transaction?: Transaction }) {
721787
return this.#executeTransaction(
722788
this.tx.rotateEncryptionKey({ transaction, ...callOptions }),
723789
signer,
@@ -729,7 +795,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
729795
* Atomically removes one or more members and rotates the encryption key.
730796
* Ensures removed members cannot decrypt new messages.
731797
*/
732-
async removeMembersAndRotateKey({ signer, transaction, ...callOptions }: RemoveMembersAndRotateKeyOptions & { transaction?: Transaction }) {
798+
async removeMembersAndRotateKey({
799+
signer,
800+
transaction,
801+
...callOptions
802+
}: RemoveMembersAndRotateKeyOptions & { transaction?: Transaction }) {
733803
return this.#executeTransaction(
734804
this.tx.removeMembersAndRotateKey({ transaction, ...callOptions }),
735805
signer,
@@ -740,7 +810,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
740810
/**
741811
* Removes the transaction sender from a messaging group.
742812
*/
743-
async leave({ signer, transaction, ...callOptions }: LeaveOptions & { transaction?: Transaction }) {
813+
async leave({
814+
signer,
815+
transaction,
816+
...callOptions
817+
}: LeaveOptions & { transaction?: Transaction }) {
744818
return this.#executeTransaction(
745819
this.tx.leave({ transaction, ...callOptions }),
746820
signer,
@@ -756,7 +830,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
756830
*
757831
* After this call the group is paused and cannot be mutated.
758832
*/
759-
async archiveGroup({ signer, transaction, ...callOptions }: ArchiveGroupOptions & { transaction?: Transaction }) {
833+
async archiveGroup({
834+
signer,
835+
transaction,
836+
...callOptions
837+
}: ArchiveGroupOptions & { transaction?: Transaction }) {
760838
return this.#executeTransaction(
761839
this.tx.archiveGroup({ transaction, ...callOptions }),
762840
signer,
@@ -770,7 +848,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
770848
* Sets the group name.
771849
* Requires `MetadataAdmin` permission.
772850
*/
773-
async setGroupName({ signer, transaction, ...callOptions }: SetGroupNameOptions & { transaction?: Transaction }) {
851+
async setGroupName({
852+
signer,
853+
transaction,
854+
...callOptions
855+
}: SetGroupNameOptions & { transaction?: Transaction }) {
774856
return this.#executeTransaction(
775857
this.tx.setGroupName({ transaction, ...callOptions }),
776858
signer,
@@ -782,7 +864,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
782864
* Inserts a key-value pair into the group's metadata data map.
783865
* Requires `MetadataAdmin` permission.
784866
*/
785-
async insertGroupData({ signer, transaction, ...callOptions }: InsertGroupDataOptions & { transaction?: Transaction }) {
867+
async insertGroupData({
868+
signer,
869+
transaction,
870+
...callOptions
871+
}: InsertGroupDataOptions & { transaction?: Transaction }) {
786872
return this.#executeTransaction(
787873
this.tx.insertGroupData({ transaction, ...callOptions }),
788874
signer,
@@ -794,7 +880,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
794880
* Removes a key-value pair from the group's metadata data map.
795881
* Requires `MetadataAdmin` permission.
796882
*/
797-
async removeGroupData({ signer, transaction, ...callOptions }: RemoveGroupDataOptions & { transaction?: Transaction }) {
883+
async removeGroupData({
884+
signer,
885+
transaction,
886+
...callOptions
887+
}: RemoveGroupDataOptions & { transaction?: Transaction }) {
798888
return this.#executeTransaction(
799889
this.tx.removeGroupData({ transaction, ...callOptions }),
800890
signer,
@@ -808,7 +898,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
808898
* Sets a SuiNS reverse lookup on a messaging group.
809899
* Requires `ExtensionPermissionsAdmin` permission on the group.
810900
*/
811-
async setSuinsReverseLookup({ signer, transaction, ...callOptions }: SetSuinsReverseLookupOptions & { transaction?: Transaction }) {
901+
async setSuinsReverseLookup({
902+
signer,
903+
transaction,
904+
...callOptions
905+
}: SetSuinsReverseLookupOptions & { transaction?: Transaction }) {
812906
return this.#executeTransaction(
813907
this.tx.setSuinsReverseLookup({ transaction, ...callOptions }),
814908
signer,
@@ -820,7 +914,11 @@ export class MessagingGroupsClient<TApproveContext = void> {
820914
* Unsets a SuiNS reverse lookup on a messaging group.
821915
* Requires `ExtensionPermissionsAdmin` permission on the group.
822916
*/
823-
async unsetSuinsReverseLookup({ signer, transaction, ...callOptions }: UnsetSuinsReverseLookupOptions & { transaction?: Transaction }) {
917+
async unsetSuinsReverseLookup({
918+
signer,
919+
transaction,
920+
...callOptions
921+
}: UnsetSuinsReverseLookupOptions & { transaction?: Transaction }) {
824922
return this.#executeTransaction(
825923
this.tx.unsetSuinsReverseLookup({ transaction, ...callOptions }),
826924
signer,

ts-sdks/packages/messaging-groups/src/factory.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import { MessagingGroupsClientError } from './error.js';
1818
import type { AttachmentsConfig } from './attachments/types.js';
1919
import type { RelayerConfig } from './relayer/types.js';
20+
import type { RecoveryTransport } from './recovery/transport.js';
2021
import type { MessagingGroupsEncryptionOptions, MessagingGroupsPackageConfig } from './types.js';
2122

2223
/**
@@ -55,6 +56,9 @@ export interface CreateMessagingGroupsClientOptions<TApproveContext = void> {
5556

5657
/** Attachment support. When omitted, messages cannot include files. */
5758
attachments?: AttachmentsConfig;
59+
60+
/** Optional recovery transport for fetching messages from an alternative storage backend. */
61+
recovery?: RecoveryTransport;
5862
}
5963

6064
/**
@@ -123,6 +127,7 @@ export function createMessagingGroupsClient<TApproveContext = void>(
123127
suinsConfig: options.suinsConfig,
124128
relayer: options.relayer,
125129
attachments: options.attachments,
130+
recovery: options.recovery,
126131
}),
127132
);
128133
}

ts-sdks/packages/messaging-groups/src/messaging-types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,18 @@ export type SubscribeOptions<TApproveContext = void> = WithApproveContext<
141141
SubscribeOptionsBase,
142142
TApproveContext
143143
>;
144+
145+
// ── Recovery ─────────────────────────────────────────────────────
146+
147+
interface RecoverMessagesOptionsBase {
148+
groupRef: GroupRef;
149+
afterOrder?: number;
150+
beforeOrder?: number;
151+
limit?: number;
152+
}
153+
154+
/** Options for {@link MessagingGroupsClient.recoverMessages}. */
155+
export type RecoverMessagesOptions<TApproveContext = void> = WithApproveContext<
156+
RecoverMessagesOptionsBase,
157+
TApproveContext
158+
>;

ts-sdks/packages/messaging-groups/src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { SuinsConfig } from './constants.js';
1212
import type { CryptoPrimitives } from './encryption/crypto-primitives.js';
1313
import type { SealPolicy } from './encryption/seal-policy.js';
1414
import type { RelayerConfig } from './relayer/types.js';
15+
import type { RecoveryTransport } from './recovery/transport.js';
1516

1617
// === Package Configuration ===
1718

@@ -135,6 +136,12 @@ export interface MessagingGroupsClientOptions<
135136
* and received attachments are not resolvable.
136137
*/
137138
attachments?: AttachmentsConfig;
139+
/**
140+
* Optional recovery transport for fetching messages from an alternative
141+
* storage backend (e.g., Walrus). When provided, enables the
142+
* `recoverMessages()` method on the client.
143+
*/
144+
recovery?: RecoveryTransport;
138145
}
139146

140147
// === Call/Tx Options (no signer) ===

0 commit comments

Comments
 (0)