@wireapp/core 29.2.3 → 30.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/package.json +3 -3
  3. package/src/main/Account.js +4 -0
  4. package/src/main/conversation/ConversationService/ConversationService.d.ts +10 -4
  5. package/src/main/conversation/ConversationService/ConversationService.js +61 -10
  6. package/src/main/conversation/ConversationService/ConversationService.types.d.ts +5 -0
  7. package/src/main/conversation/content/AssetContent.d.ts +3 -3
  8. package/src/main/conversation/content/ContentType.d.ts +2 -2
  9. package/src/main/conversation/content/ConversationContent.d.ts +2 -2
  10. package/src/main/conversation/content/EditedTextContent.d.ts +1 -1
  11. package/src/main/conversation/content/LinkPreviewContent.d.ts +1 -1
  12. package/src/main/conversation/content/LocationContent.d.ts +1 -1
  13. package/src/main/conversation/content/QuoteContent.d.ts +1 -1
  14. package/src/main/conversation/content/ReactionContent.d.ts +1 -1
  15. package/src/main/conversation/content/TextContent.d.ts +1 -1
  16. package/src/main/cryptography/CryptographyDatabaseRepository.d.ts +2 -1
  17. package/src/main/cryptography/CryptographyDatabaseRepository.js +2 -8
  18. package/src/main/notification/NotificationDatabaseRepository.d.ts +23 -7
  19. package/src/main/notification/NotificationDatabaseRepository.js +42 -9
  20. package/src/main/notification/NotificationService.d.ts +28 -0
  21. package/src/main/notification/NotificationService.js +95 -6
  22. package/src/main/notification/types.d.ts +17 -0
  23. package/src/main/notification/types.js +21 -0
  24. package/src/main/util/TaskScheduler/TaskScheduler.d.ts +10 -0
  25. package/src/main/util/TaskScheduler/TaskScheduler.js +70 -0
  26. package/src/main/util/mapQualifiedUserClientIdsToFullyQualifiedClientIds.d.ts +8 -0
  27. package/src/main/util/mapQualifiedUserClientIdsToFullyQualifiedClientIds.js +32 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,68 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [30.0.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@29.3.0...@wireapp/core@30.0.0) (2022-08-25)
7
+
8
+
9
+ ### Features
10
+
11
+ * remove user from MLS conversation (#FS-561) ([#4366](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4366)) ([89da444](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/89da4449fa3caa0cbe41fbd595326f157e7e4011)), closes [#FS-561](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/FS-561) [#4367](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4367)
12
+
13
+
14
+ ### BREAKING CHANGES
15
+
16
+ * renamed removeUser function to removeUserFromProteusConversation in @wireapp/core and
17
+ changed the types of conversation and user ids in @wireapp/bot-api MessageHandler from string to QualifiedId.
18
+
19
+ * chore: initial work on remove user from mls convo
20
+
21
+ * fix: use FQCI ids for client ids
22
+
23
+ * fix: use FQCI ids for client ids
24
+
25
+ * runfix: encode client id straight to byte array
26
+
27
+ * runfix: encode client id straight to byte array
28
+
29
+ * feat: send client-removal commit messages
30
+
31
+ * runfix: remove event from remove user from proteus convo func
32
+
33
+ * refactor: rename proteus remove func
34
+
35
+ * test: add test for fully qualified ids mapping function
36
+
37
+ * refactor: more precise name for remove from proteus convo
38
+
39
+ * refactor: apply CR suggestions
40
+
41
+ * runfix: don't convert messages twice to uint8arr
42
+
43
+ * runfix: wrapp commits into uint8arr
44
+
45
+
46
+
47
+
48
+
49
+ # [29.3.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@29.2.4...@wireapp/core@29.3.0) (2022-08-25)
50
+
51
+
52
+ ### Features
53
+
54
+ * periodically commit proposals (fs-690) ([#4362](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4362)) ([d821f9e](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/d821f9e43d35b4cb151588f5a7b18aa503e39410))
55
+
56
+
57
+
58
+
59
+
60
+ ## [29.2.4](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@29.2.3...@wireapp/core@29.2.4) (2022-08-23)
61
+
62
+ **Note:** Version bump only for package @wireapp/core
63
+
64
+
65
+
66
+
67
+
6
68
  ## [29.2.3](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@29.2.2...@wireapp/core@29.2.3) (2022-08-23)
7
69
 
8
70
  **Note:** Version bump only for package @wireapp/core
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "@otak/core-crypto": "0.3.0-es2017",
8
8
  "@types/long": "4.0.1",
9
9
  "@types/node": "~14",
10
- "@wireapp/api-client": "19.21.5",
10
+ "@wireapp/api-client": "20.0.0",
11
11
  "@wireapp/commons": "4.3.0",
12
12
  "@wireapp/cryptobox": "12.8.0",
13
13
  "@wireapp/store-engine-dexie": "1.6.10",
@@ -77,6 +77,6 @@
77
77
  "test:node": "nyc jasmine --config=jasmine.json",
78
78
  "watch": "tsc ---watch"
79
79
  },
80
- "version": "29.2.3",
81
- "gitHead": "424af23038341c7ca52ac14631fc14bf9d474671"
80
+ "version": "30.0.0",
81
+ "gitHead": "080d1139d0b9e8c3053d42dd42492a1a9b523191"
82
82
  }
@@ -137,10 +137,14 @@ class Account extends events_1.EventEmitter {
137
137
  * @param cookie The cookie to identify the user against backend (will use the browser's one if not given)
138
138
  */
139
139
  async init(clientType, cookie, initClient = true) {
140
+ var _a;
140
141
  const context = await this.apiClient.init(clientType, cookie);
141
142
  await this.initServices(context);
143
+ // Assumption: client gets only initialized once
142
144
  if (initClient) {
143
145
  await this.initClient({ clientType });
146
+ // initialize schedulers for pending mls proposals once client is initialized
147
+ await ((_a = this.service) === null || _a === void 0 ? void 0 : _a.notification.checkExistingPendingProposals());
144
148
  }
145
149
  return context;
146
150
  }
@@ -3,8 +3,8 @@ import type { APIClient } from '@wireapp/api-client';
3
3
  import { MessageSendingStatus, Conversation, DefaultConversationRoleName, MutedStatus, NewConversation, QualifiedUserClients, UserClients, ClientMismatch } from '@wireapp/api-client/src/conversation';
4
4
  import type { ConversationMemberLeaveEvent } from '@wireapp/api-client/src/event';
5
5
  import type { QualifiedId, UserPreKeyBundleMap } from '@wireapp/api-client/src/user';
6
- import { MessageTimer } from '../../conversation/';
7
- import type { RemoteData } from '../../conversation/content/';
6
+ import { MessageTimer, RemoveUsersParams } from '../../conversation/';
7
+ import type { RemoteData } from '../content';
8
8
  import type { CryptographyService } from '../../cryptography/';
9
9
  import type { ClearConversationMessage, DeleteMessage, HideMessage, OtrMessage } from '../message/OtrMessage';
10
10
  import { XOR } from '@wireapp/commons/src/main/util/TypeUtil';
@@ -102,7 +102,11 @@ export declare class ConversationService {
102
102
  getAllParticipantsClients(conversationId: string, conversationDomain?: string): Promise<UserClients | QualifiedUserClients>;
103
103
  deleteMessageLocal(conversationId: string, messageIdToHide: string, sendAsProtobuf?: boolean, conversationDomain?: string): Promise<HideMessage>;
104
104
  deleteMessageEveryone(conversationId: string, messageIdToDelete: string, userIds?: string[] | QualifiedId[] | UserClients | QualifiedUserClients, sendAsProtobuf?: boolean, conversationDomain?: string, callbacks?: MessageSendingCallbacks): Promise<DeleteMessage>;
105
- leaveConversation(conversationId: string): Promise<ConversationMemberLeaveEvent>;
105
+ leaveConversation(conversationId: QualifiedId): Promise<ConversationMemberLeaveEvent>;
106
+ leaveMLSConversation(conversationId: QualifiedId): Promise<ConversationMemberLeaveEvent>;
107
+ /**
108
+ * @depricated seems not to be used and is outdated. use leaveConversation instead
109
+ */
106
110
  leaveConversations(conversationIds?: string[]): Promise<ConversationMemberLeaveEvent[]>;
107
111
  /**
108
112
  * Create a group conversation.
@@ -127,7 +131,7 @@ export declare class ConversationService {
127
131
  getAsset({ assetId, assetToken, otrKey, sha256 }: RemoteData): Promise<Uint8Array>;
128
132
  getUnencryptedAsset(assetId: string, assetToken?: string): Promise<ArrayBuffer>;
129
133
  addUsersToProteusConversation({ conversationId, qualifiedUserIds }: Omit<AddUsersParams, 'groupId'>): Promise<import("@wireapp/api-client/src/event").ConversationMemberJoinEvent>;
130
- removeUser(conversationId: string, userId: string): Promise<string>;
134
+ removeUserFromProteusConversation(conversationId: QualifiedId, userId: QualifiedId): Promise<ConversationMemberLeaveEvent>;
131
135
  private sendProteusMessage;
132
136
  /**
133
137
  * Sends a message to a conversation
@@ -151,4 +155,6 @@ export declare class ConversationService {
151
155
  createMLSConversation(conversationData: NewConversation): Promise<MLSReturnType>;
152
156
  private sendMLSMessage;
153
157
  addUsersToMLSConversation({ qualifiedUserIds, groupId, conversationId, }: Required<AddUsersParams>): Promise<MLSReturnType>;
158
+ private sendCommitBundleRemovalMessages;
159
+ removeUsersFromMLSConversation({ groupId, conversationId, qualifiedUserIds, }: RemoveUsersParams): Promise<MLSReturnType>;
154
160
  }
@@ -30,6 +30,12 @@ const MessageService_1 = require("../message/MessageService");
30
30
  const MessageToProtoMapper_1 = require("../message/MessageToProtoMapper");
31
31
  const ConversationService_types_1 = require("./ConversationService.types");
32
32
  const bazinga64_1 = require("bazinga64");
33
+ const mapQualifiedUserClientIdsToFullyQualifiedClientIds_1 = require("../../util/mapQualifiedUserClientIdsToFullyQualifiedClientIds");
34
+ //@todo: this function is temporary, we wait for the update from core-crypto side
35
+ //they are returning regular array instead of Uint8Array for commit and welcome messages
36
+ const optionalToUint8Array = (array) => {
37
+ return Array.isArray(array) ? Uint8Array.from(array) : array;
38
+ };
33
39
  class ConversationService {
34
40
  constructor(apiClient, cryptographyService, config, coreCryptoClientProvider, notificationService) {
35
41
  this.apiClient = apiClient;
@@ -598,8 +604,20 @@ class ConversationService {
598
604
  };
599
605
  }
600
606
  leaveConversation(conversationId) {
601
- return this.apiClient.api.conversation.deleteMember(conversationId, this.apiClient.context.userId);
607
+ if (!this.apiClient.context || !this.apiClient.context.userId || !this.apiClient.context.domain) {
608
+ throw new Error('Cannot leave conversation without a userId and domain');
609
+ }
610
+ return this.apiClient.api.conversation.deleteMember(conversationId, {
611
+ domain: this.apiClient.context.domain,
612
+ id: this.apiClient.context.userId,
613
+ });
614
+ }
615
+ leaveMLSConversation(conversationId) {
616
+ return this.leaveConversation(conversationId);
602
617
  }
618
+ /**
619
+ * @depricated seems not to be used and is outdated. use leaveConversation instead
620
+ */
603
621
  async leaveConversations(conversationIds) {
604
622
  if (!conversationIds) {
605
623
  const conversation = await this.getConversations();
@@ -607,7 +625,7 @@ class ConversationService {
607
625
  .filter(conversation => conversation.type === conversation_1.CONVERSATION_TYPE.REGULAR)
608
626
  .map(conversation => conversation.id);
609
627
  }
610
- return Promise.all(conversationIds.map(conversationId => this.leaveConversation(conversationId)));
628
+ return Promise.all(conversationIds.map(conversationId => { var _a, _b; return this.leaveConversation({ id: conversationId, domain: (_b = (_a = this.apiClient.context) === null || _a === void 0 ? void 0 : _a.domain) !== null && _b !== void 0 ? _b : '' }); }));
611
629
  }
612
630
  createProteusConversation(conversationData, otherUserIds) {
613
631
  let payload;
@@ -647,12 +665,10 @@ class ConversationService {
647
665
  return (await request.response).buffer;
648
666
  }
649
667
  async addUsersToProteusConversation({ conversationId, qualifiedUserIds }) {
650
- const response = await this.apiClient.api.conversation.postMembers(conversationId, qualifiedUserIds);
651
- return response;
668
+ return this.apiClient.api.conversation.postMembers(conversationId, qualifiedUserIds);
652
669
  }
653
- async removeUser(conversationId, userId) {
654
- await this.apiClient.api.conversation.deleteMember(conversationId, userId);
655
- return userId;
670
+ async removeUserFromProteusConversation(conversationId, userId) {
671
+ return this.apiClient.api.conversation.deleteMember(conversationId, userId);
656
672
  }
657
673
  async sendProteusMessage(params, genericMessage, content) {
658
674
  var _a;
@@ -822,10 +838,13 @@ class ConversationService {
822
838
  const coreCryptoClient = this.coreCryptoClientProvider();
823
839
  const memberAddedMessages = await coreCryptoClient.addClientsToConversation(groupIdDecodedFromBase64, invitee);
824
840
  if (memberAddedMessages === null || memberAddedMessages === void 0 ? void 0 : memberAddedMessages.welcome) {
825
- await this.apiClient.api.conversation.postMlsWelcomeMessage(Uint8Array.from(memberAddedMessages.welcome));
841
+ //@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
842
+ await this.apiClient.api.conversation.postMlsWelcomeMessage(optionalToUint8Array(memberAddedMessages.welcome));
826
843
  }
827
844
  if (memberAddedMessages === null || memberAddedMessages === void 0 ? void 0 : memberAddedMessages.commit) {
828
- const messageResponse = await this.apiClient.api.conversation.postMlsMessage(Uint8Array.from(memberAddedMessages.commit));
845
+ const messageResponse = await this.apiClient.api.conversation.postMlsMessage(
846
+ //@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
847
+ optionalToUint8Array(memberAddedMessages.commit));
829
848
  await coreCryptoClient.commitAccepted(groupIdDecodedFromBase64);
830
849
  return messageResponse;
831
850
  }
@@ -839,6 +858,9 @@ class ConversationService {
839
858
  */
840
859
  const newConversation = await this.apiClient.api.conversation.postConversation(Object.assign(Object.assign({}, conversationData), { users: undefined, qualified_users: undefined }));
841
860
  const { group_id: groupId, qualified_id: qualifiedId } = newConversation;
861
+ if (!groupId) {
862
+ throw new Error('No group_id found in response which is required for creating MLS conversations.');
863
+ }
842
864
  const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
843
865
  const { qualified_users: qualifiedUsers = [], selfUserId } = conversationData;
844
866
  if (!selfUserId) {
@@ -871,6 +893,8 @@ class ConversationService {
871
893
  var _a, _b;
872
894
  const { groupId, onSuccess, payload } = params;
873
895
  const groupIdBytes = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
896
+ // immediately execute pending commits before sending the message
897
+ await this.notificationService.commitPendingProposals({ groupId });
874
898
  const coreCryptoClient = this.coreCryptoClientProvider();
875
899
  const encrypted = await coreCryptoClient.encryptMessage(groupIdBytes, protocol_messaging_1.GenericMessage.encode(genericMessage).finish());
876
900
  try {
@@ -886,13 +910,40 @@ class ConversationService {
886
910
  const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
887
911
  const coreCryptoKeyPackagesPayload = await this.getCoreCryptoKeyPackagesPayload([...qualifiedUserIds]);
888
912
  const response = await this.addUsersToExistingMLSConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
889
- console.info('addUsersToMLSGroup', conversationId, qualifiedUserIds, groupIdDecodedFromBase64, response);
890
913
  const conversation = await this.getConversations(conversationId.id);
891
914
  return {
892
915
  events: (response === null || response === void 0 ? void 0 : response.events) || [],
893
916
  conversation,
894
917
  };
895
918
  }
919
+ async sendCommitBundleRemovalMessages(groupIdDecodedFromBase64, commitBundle) {
920
+ const coreCryptoClient = this.coreCryptoClientProvider();
921
+ if (commitBundle === null || commitBundle === void 0 ? void 0 : commitBundle.welcome) {
922
+ //@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
923
+ await this.apiClient.api.conversation.postMlsWelcomeMessage(optionalToUint8Array(commitBundle.welcome));
924
+ }
925
+ if (commitBundle === null || commitBundle === void 0 ? void 0 : commitBundle.commit) {
926
+ const messageResponse = await this.apiClient.api.conversation.postMlsMessage(
927
+ //@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
928
+ optionalToUint8Array(commitBundle.commit));
929
+ await coreCryptoClient.commitAccepted(groupIdDecodedFromBase64);
930
+ return messageResponse;
931
+ }
932
+ return null;
933
+ }
934
+ async removeUsersFromMLSConversation({ groupId, conversationId, qualifiedUserIds, }) {
935
+ const coreCryptoClient = this.coreCryptoClientProvider();
936
+ const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
937
+ const clientsToRemove = await this.apiClient.api.user.postListClients({ qualified_users: qualifiedUserIds });
938
+ const fullyQualifiedClientIds = (0, mapQualifiedUserClientIdsToFullyQualifiedClientIds_1.mapQualifiedUserClientIdsToFullyQualifiedClientIds)(clientsToRemove.qualified_user_map);
939
+ const commitBundle = await coreCryptoClient.removeClientsFromConversation(groupIdDecodedFromBase64, fullyQualifiedClientIds);
940
+ const messageResponse = await this.sendCommitBundleRemovalMessages(groupIdDecodedFromBase64, commitBundle);
941
+ const conversation = await this.getConversations(conversationId.id);
942
+ return {
943
+ events: (messageResponse === null || messageResponse === void 0 ? void 0 : messageResponse.events) || [],
944
+ conversation,
945
+ };
946
+ }
896
947
  }
897
948
  exports.ConversationService = ConversationService;
898
949
  //# sourceMappingURL=ConversationService.js.map
@@ -97,6 +97,11 @@ export declare type AddUsersParams = {
97
97
  qualifiedUserIds: QualifiedId[];
98
98
  groupId?: string;
99
99
  };
100
+ export declare type RemoveUsersParams = {
101
+ conversationId: QualifiedId;
102
+ qualifiedUserIds: QualifiedId[];
103
+ groupId: string;
104
+ };
100
105
  export declare type MLSReturnType = {
101
106
  events: MlsEvent[];
102
107
  conversation: Conversation;
@@ -1,8 +1,8 @@
1
1
  /// <reference types="node" />
2
2
  import type { Asset } from '@wireapp/protocol-messaging';
3
- import type { AbortReason, AssetTransferState } from '../../conversation/';
4
- import type { FileContent, FileMetaDataContent, ImageContent, LegalHoldStatus } from '../../conversation/content/';
5
- import type { EncryptedAssetUploaded } from '../../cryptography/';
3
+ import type { AbortReason, AssetTransferState } from '..';
4
+ import type { FileContent, FileMetaDataContent, ImageContent, LegalHoldStatus } from '.';
5
+ import type { EncryptedAssetUploaded } from '../../cryptography';
6
6
  export declare type ImageMetaData = Asset.IImageMetaData;
7
7
  export declare type VideoMetaData = Asset.IVideoMetaData;
8
8
  export declare type Preview = Asset.IPreview;
@@ -1,6 +1,6 @@
1
1
  import type { Connection } from '@wireapp/api-client/src/connection/';
2
- import type { ClientActionType } from '../';
3
- import type { AssetContent, ClearedContent, ClientActionContent, ConfirmationContent, ConversationContent, DeletedContent, EditedTextContent, FileAssetAbortContent, FileAssetContent, FileAssetMetaDataContent, HiddenContent, ImageAssetContent, ImageContent, LocationContent, ReactionContent, TextContent } from '../content/';
2
+ import type { ClientActionType } from '..';
3
+ import type { AssetContent, ClearedContent, ClientActionContent, ConfirmationContent, ConversationContent, DeletedContent, EditedTextContent, FileAssetAbortContent, FileAssetContent, FileAssetMetaDataContent, HiddenContent, ImageAssetContent, ImageContent, LocationContent, ReactionContent, TextContent } from '.';
4
4
  export declare function isAbortedAssetContent(content: ConversationContent): content is AssetContent;
5
5
  export declare function isAssetContent(content: ConversationContent): content is AssetContent;
6
6
  export declare function isClearedContent(content: ConversationContent): content is ClearedContent;
@@ -1,3 +1,3 @@
1
- import type { ClientActionType } from '../';
2
- import type { AssetContent, ButtonActionContent, ButtonActionConfirmationContent, CallingContent, ClearedContent, ClientActionContent, ClientAddContent, ClientRemoveContent, CompositeContent, ConfirmationContent, ConnectionContent, DeletedContent, EditedTextContent, FileAssetAbortContent, FileAssetContent, FileAssetMetaDataContent, HiddenContent, ImageAssetContent, ImageContent, KnockContent, LocationContent, ReactionContent, TextContent } from './';
1
+ import type { ClientActionType } from '..';
2
+ import type { AssetContent, ButtonActionContent, ButtonActionConfirmationContent, CallingContent, ClearedContent, ClientActionContent, ClientAddContent, ClientRemoveContent, CompositeContent, ConfirmationContent, ConnectionContent, DeletedContent, EditedTextContent, FileAssetAbortContent, FileAssetContent, FileAssetMetaDataContent, HiddenContent, ImageAssetContent, ImageContent, KnockContent, LocationContent, ReactionContent, TextContent } from '.';
3
3
  export declare type ConversationContent = AssetContent | ButtonActionContent | ButtonActionConfirmationContent | CallingContent | ClearedContent | ClientActionContent | ClientActionType | ClientAddContent | ClientRemoveContent | CompositeContent | ConfirmationContent | ConnectionContent | DeletedContent | EditedTextContent | FileAssetAbortContent | FileAssetContent | FileAssetMetaDataContent | HiddenContent | ImageAssetContent | ImageContent | KnockContent | LocationContent | ReactionContent | TextContent;
@@ -1,4 +1,4 @@
1
- import type { QuoteContent, TextContent } from '../content/';
1
+ import type { QuoteContent, TextContent } from '.';
2
2
  export interface EditedTextContent extends TextContent {
3
3
  originalMessageId: string;
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  import type { ILinkPreview } from '@wireapp/protocol-messaging';
2
- import type { ImageAssetContent, ImageContent, LegalHoldStatus } from '../content/';
2
+ import type { ImageAssetContent, ImageContent, LegalHoldStatus } from '.';
3
3
  export interface LinkPreviewContent extends Omit<ILinkPreview, 'image'> {
4
4
  expectsReadConfirmation?: boolean;
5
5
  legalHoldStatus?: LegalHoldStatus;
@@ -1,4 +1,4 @@
1
- import type { LegalHoldStatus } from './';
1
+ import type { LegalHoldStatus } from '.';
2
2
  export interface LocationContent {
3
3
  expectsReadConfirmation?: boolean;
4
4
  latitude: number;
@@ -1,5 +1,5 @@
1
1
  import type { IQuote } from '@wireapp/protocol-messaging';
2
- import type { AssetContent, LocationContent, TextContent } from '../content/';
2
+ import type { AssetContent, LocationContent, TextContent } from '.';
3
3
  export { IQuote as QuoteContent };
4
4
  export interface QuoteMessageContent {
5
5
  content: AssetContent | LocationContent | TextContent;
@@ -1,5 +1,5 @@
1
1
  import type { LegalHoldStatus } from '.';
2
- import type { ReactionType } from '../';
2
+ import type { ReactionType } from '..';
3
3
  export interface ReactionContent {
4
4
  legalHoldStatus?: LegalHoldStatus;
5
5
  originalMessageId: string;
@@ -1,4 +1,4 @@
1
- import type { LegalHoldStatus, LinkPreviewUploadedContent, MentionContent, QuoteContent } from '../content/';
1
+ import type { LegalHoldStatus, LinkPreviewUploadedContent, MentionContent, QuoteContent } from '.';
2
2
  export interface TextContent {
3
3
  expectsReadConfirmation?: boolean;
4
4
  legalHoldStatus?: LegalHoldStatus;
@@ -5,7 +5,8 @@ export declare enum DatabaseStores {
5
5
  KEYS = "keys",
6
6
  PRE_KEYS = "prekeys",
7
7
  SESSIONS = "sessions",
8
- GROUP_IDS = "group_ids"
8
+ GROUP_IDS = "group_ids",
9
+ PENDING_PROPOSALS = "pending_proposals"
9
10
  }
10
11
  export declare class CryptographyDatabaseRepository {
11
12
  private readonly storeEngine;
@@ -27,20 +27,14 @@ var DatabaseStores;
27
27
  DatabaseStores["PRE_KEYS"] = "prekeys";
28
28
  DatabaseStores["SESSIONS"] = "sessions";
29
29
  DatabaseStores["GROUP_IDS"] = "group_ids";
30
+ DatabaseStores["PENDING_PROPOSALS"] = "pending_proposals";
30
31
  })(DatabaseStores = exports.DatabaseStores || (exports.DatabaseStores = {}));
31
32
  class CryptographyDatabaseRepository {
32
33
  constructor(storeEngine) {
33
34
  this.storeEngine = storeEngine;
34
35
  }
35
36
  deleteStores() {
36
- return Promise.all([
37
- this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.AMPLIFY),
38
- this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.CLIENTS),
39
- this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.KEYS),
40
- this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.SESSIONS),
41
- this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.PRE_KEYS),
42
- this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.GROUP_IDS),
43
- ]);
37
+ return Promise.all(Object.keys(CryptographyDatabaseRepository.STORES).map(store => this.storeEngine.deleteAll(store)));
44
38
  }
45
39
  }
46
40
  exports.CryptographyDatabaseRepository = CryptographyDatabaseRepository;
@@ -1,11 +1,7 @@
1
1
  import type { BackendEvent } from '@wireapp/api-client/src/event';
2
2
  import type { Notification } from '@wireapp/api-client/src/notification/';
3
3
  import type { CRUDEngine } from '@wireapp/store-engine';
4
- declare type CompoundGroupIdParams = {
5
- groupId: string;
6
- conversationId: string;
7
- conversationDomain: string;
8
- };
4
+ import { CommonMLS, CompoundGroupIdParams, StorePendingProposalsParams } from './types';
9
5
  export declare enum DatabaseStores {
10
6
  EVENTS = "events"
11
7
  }
@@ -23,7 +19,27 @@ export declare class NotificationDatabaseRepository {
23
19
  getLastNotificationId(): Promise<string>;
24
20
  updateLastNotificationId(lastNotification: Notification): Promise<string>;
25
21
  private generateCompoundGroupIdPrimaryKey;
26
- addCompoundGroupId(params: CompoundGroupIdParams): Promise<void>;
22
+ addCompoundGroupId(params: CompoundGroupIdParams): Promise<CompoundGroupIdParams>;
27
23
  getCompoundGroupId(params: Omit<CompoundGroupIdParams, 'groupId'>): Promise<string>;
24
+ /**
25
+ * ## MLS only ##
26
+ * Store groupIds with pending proposals and a delay in the DB until the proposals get committed.
27
+ *
28
+ * @param groupId groupId of the mls conversation
29
+ * @param firingDate date when the pending proposals should be committed
30
+ */
31
+ storePendingProposal(params: StorePendingProposalsParams): Promise<boolean>;
32
+ /**
33
+ * ## MLS only ##
34
+ * Delete stored entries for pending proposals that have been committed.
35
+ *
36
+ * @param groupId groupId of the mls conversation
37
+ */
38
+ deletePendingProposal({ groupId }: CommonMLS): Promise<boolean>;
39
+ /**
40
+ * ## MLS only ##
41
+ * Get all stored entries for pending proposals.
42
+ *
43
+ */
44
+ getStoredPendingProposals(): Promise<StorePendingProposalsParams[]>;
28
45
  }
29
- export {};
@@ -29,8 +29,7 @@ var DatabaseKeys;
29
29
  DatabaseKeys["PRIMARY_KEY_LAST_EVENT"] = "z.storage.StorageKey.EVENT.LAST_DATE";
30
30
  DatabaseKeys["PRIMARY_KEY_LAST_NOTIFICATION"] = "z.storage.StorageKey.NOTIFICATION.LAST_ID";
31
31
  })(DatabaseKeys = exports.DatabaseKeys || (exports.DatabaseKeys = {}));
32
- const STORE_AMPLIFY = CryptographyDatabaseRepository_1.CryptographyDatabaseRepository.STORES.AMPLIFY;
33
- const STORE_GROUPIDS = CryptographyDatabaseRepository_1.CryptographyDatabaseRepository.STORES.GROUP_IDS;
32
+ const STORES = Object.assign({}, CryptographyDatabaseRepository_1.CryptographyDatabaseRepository.STORES);
34
33
  class NotificationDatabaseRepository {
35
34
  constructor(storeEngine) {
36
35
  this.storeEngine = storeEngine;
@@ -39,23 +38,27 @@ class NotificationDatabaseRepository {
39
38
  return this.storeEngine.readAll(DatabaseStores.EVENTS);
40
39
  }
41
40
  async getLastEventDate() {
42
- const { value } = await this.storeEngine.read(STORE_AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_EVENT);
41
+ const { value } = await this.storeEngine.read(STORES.AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_EVENT);
43
42
  return new Date(value);
44
43
  }
45
44
  async updateLastEventDate(eventDate) {
46
- await this.storeEngine.update(STORE_AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_EVENT, { value: eventDate.toISOString() });
45
+ await this.storeEngine.update(STORES.AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_EVENT, {
46
+ value: eventDate.toISOString(),
47
+ });
47
48
  return eventDate;
48
49
  }
49
50
  async createLastEventDate(eventDate) {
50
- await this.storeEngine.create(STORE_AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_EVENT, { value: eventDate.toISOString() });
51
+ await this.storeEngine.create(STORES.AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_EVENT, {
52
+ value: eventDate.toISOString(),
53
+ });
51
54
  return eventDate;
52
55
  }
53
56
  async getLastNotificationId() {
54
- const { value } = await this.storeEngine.read(STORE_AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_NOTIFICATION);
57
+ const { value } = await this.storeEngine.read(STORES.AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_NOTIFICATION);
55
58
  return value;
56
59
  }
57
60
  async updateLastNotificationId(lastNotification) {
58
- await this.storeEngine.updateOrCreate(STORE_AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_NOTIFICATION, {
61
+ await this.storeEngine.updateOrCreate(STORES.AMPLIFY, DatabaseKeys.PRIMARY_KEY_LAST_NOTIFICATION, {
59
62
  value: lastNotification.id,
60
63
  });
61
64
  return lastNotification.id;
@@ -64,10 +67,40 @@ class NotificationDatabaseRepository {
64
67
  return `${conversationId}@${conversationDomain}`;
65
68
  }
66
69
  async addCompoundGroupId(params) {
67
- await this.storeEngine.updateOrCreate(STORE_GROUPIDS, this.generateCompoundGroupIdPrimaryKey(params), params.groupId);
70
+ await this.storeEngine.updateOrCreate(STORES.GROUP_IDS, this.generateCompoundGroupIdPrimaryKey(params), params.groupId);
71
+ return params;
68
72
  }
69
73
  async getCompoundGroupId(params) {
70
- return this.storeEngine.read(STORE_GROUPIDS, this.generateCompoundGroupIdPrimaryKey(params));
74
+ return this.storeEngine.read(STORES.GROUP_IDS, this.generateCompoundGroupIdPrimaryKey(params));
75
+ }
76
+ /**
77
+ * ## MLS only ##
78
+ * Store groupIds with pending proposals and a delay in the DB until the proposals get committed.
79
+ *
80
+ * @param groupId groupId of the mls conversation
81
+ * @param firingDate date when the pending proposals should be committed
82
+ */
83
+ async storePendingProposal(params) {
84
+ await this.storeEngine.updateOrCreate(STORES.PENDING_PROPOSALS, `${params.groupId}`, params);
85
+ return true;
86
+ }
87
+ /**
88
+ * ## MLS only ##
89
+ * Delete stored entries for pending proposals that have been committed.
90
+ *
91
+ * @param groupId groupId of the mls conversation
92
+ */
93
+ async deletePendingProposal({ groupId }) {
94
+ await this.storeEngine.delete(STORES.PENDING_PROPOSALS, `${groupId}`);
95
+ return true;
96
+ }
97
+ /**
98
+ * ## MLS only ##
99
+ * Get all stored entries for pending proposals.
100
+ *
101
+ */
102
+ async getStoredPendingProposals() {
103
+ return this.storeEngine.readAll(STORES.PENDING_PROPOSALS);
71
104
  }
72
105
  }
73
106
  exports.NotificationDatabaseRepository = NotificationDatabaseRepository;
@@ -12,6 +12,7 @@ import { AbortHandler } from '@wireapp/api-client/src/tcp';
12
12
  import type { CoreCrypto } from '@otak/core-crypto/platforms/web/corecrypto';
13
13
  import { QualifiedId } from '@wireapp/api-client/src/user';
14
14
  import { Conversation } from '@wireapp/api-client/src/conversation';
15
+ import { CommitPendingProposalsParams } from './types';
15
16
  export declare type HandledEventPayload = {
16
17
  event: Events.BackendEvent;
17
18
  mappedEvent?: PayloadBundle;
@@ -61,6 +62,7 @@ export declare class NotificationService extends EventEmitter {
61
62
  private cleanupPayloadBundle;
62
63
  private handleEvent;
63
64
  /**
65
+ * ## MLS only ##
64
66
  * If there is a groupId in the conversation, we need to store the conversationId => groupId pair
65
67
  * in order to find the groupId when decrypting messages
66
68
  * This is a bit hacky but since mls messages do not embed the groupId we need to keep a mapping of those
@@ -69,11 +71,37 @@ export declare class NotificationService extends EventEmitter {
69
71
  */
70
72
  saveConversationGroupId(conversation: Conversation): Promise<void>;
71
73
  /**
74
+ * ## MLS only ##
72
75
  * If there is a matching conversationId => groupId pair in the database,
73
76
  * we can find the groupId and return it as a Uint8Array
74
77
  *
75
78
  * @param conversationQualifiedId
76
79
  */
77
80
  getUint8ArrayFromConversationGroupId(conversationQualifiedId: QualifiedId): Promise<Uint8Array>;
81
+ /**
82
+ * ## MLS only ##
83
+ * If there are pending proposals, we need to either process them,
84
+ * or save them in the database for later processing
85
+ *
86
+ * @param groupId groupId of the mls conversation
87
+ * @param delayInMs delay in ms before processing proposals
88
+ * @param eventTime time of the event that had the proposals
89
+ */
90
+ private handlePendingProposals;
91
+ /**
92
+ * ## MLS only ##
93
+ * Commit all pending proposals for a given groupId
94
+ *
95
+ * @param groupId groupId of the conversation
96
+ * @param skipDelete if true, do not delete the pending proposals from the database
97
+ */
98
+ commitPendingProposals({ groupId, skipDelete }: CommitPendingProposalsParams): Promise<void>;
99
+ /**
100
+ * ## MLS only ##
101
+ * Get all pending proposals from the database and schedule them
102
+ * Function must only be called once, after application start
103
+ *
104
+ */
105
+ checkExistingPendingProposals(): Promise<void>;
78
106
  }
79
107
  export {};
@@ -65,6 +65,7 @@ const NotificationBackendRepository_1 = require("./NotificationBackendRepository
65
65
  const NotificationDatabaseRepository_1 = require("./NotificationDatabaseRepository");
66
66
  const protocol_messaging_1 = require("@wireapp/protocol-messaging");
67
67
  const bazinga64_1 = require("bazinga64");
68
+ const TaskScheduler_1 = require("../util/TaskScheduler/TaskScheduler");
68
69
  var TOPIC;
69
70
  (function (TOPIC) {
70
71
  TOPIC["NOTIFICATION_ERROR"] = "NotificationService.TOPIC.NOTIFICATION_ERROR";
@@ -209,7 +210,7 @@ class NotificationService extends events_1.EventEmitter {
209
210
  }
210
211
  }
211
212
  async handleEvent(event, source, dryRun = false) {
212
- var _a;
213
+ var _a, _b;
213
214
  const coreCryptoClient = this.coreCryptoClientProvider();
214
215
  if (!coreCryptoClient) {
215
216
  throw new Error('Unable to access core crypto client');
@@ -227,12 +228,20 @@ class NotificationService extends events_1.EventEmitter {
227
228
  };
228
229
  case Events.CONVERSATION_EVENT.MLS_MESSAGE_ADD:
229
230
  const encryptedData = bazinga64_1.Decoder.fromBase64(event.data).asBytes;
230
- const groupId = await this.getUint8ArrayFromConversationGroupId(event.qualified_conversation || { id: event.conversation, domain: '' });
231
- const rawData = await coreCryptoClient.decryptMessage(groupId, encryptedData);
232
- if (!rawData.message) {
231
+ const groupId = await this.getUint8ArrayFromConversationGroupId((_a = event.qualified_conversation) !== null && _a !== void 0 ? _a : { id: event.conversation, domain: '' });
232
+ // Check if the message includes proposals
233
+ const { proposals, commitDelay, message } = await coreCryptoClient.decryptMessage(groupId, encryptedData);
234
+ if (proposals.length > 0) {
235
+ await this.handlePendingProposals({
236
+ groupId: groupId.toString(),
237
+ delayInMs: commitDelay !== null && commitDelay !== void 0 ? commitDelay : 0,
238
+ eventTime: event.time,
239
+ });
240
+ }
241
+ if (!message) {
233
242
  throw new Error(`MLS message received from ${source} was empty`);
234
243
  }
235
- const decryptedData = protocol_messaging_1.GenericMessage.decode(rawData.message);
244
+ const decryptedData = protocol_messaging_1.GenericMessage.decode(message);
236
245
  /**
237
246
  * @todo Find a proper solution to add mappedEvent to this return
238
247
  * otherwise event.data will be base64 raw data of the received event
@@ -261,7 +270,7 @@ class NotificationService extends events_1.EventEmitter {
261
270
  case Events.CONVERSATION_EVENT.MEMBER_JOIN:
262
271
  // As of today (07/07/2022) the backend sends `WELCOME` message to the user's own conversation (not the actual conversation that the welcome should be part of)
263
272
  // So in order to map conversation Ids and groupId together, we need to first fetch the conversation and get the groupId linked to it.
264
- const conversation = await this.apiClient.api.conversation.getConversation((_a = event.qualified_conversation) !== null && _a !== void 0 ? _a : { id: event.conversation, domain: '' });
273
+ const conversation = await this.apiClient.api.conversation.getConversation((_b = event.qualified_conversation) !== null && _b !== void 0 ? _b : { id: event.conversation, domain: '' });
265
274
  if (!conversation) {
266
275
  throw new Error('no conv');
267
276
  }
@@ -284,6 +293,7 @@ class NotificationService extends events_1.EventEmitter {
284
293
  return { event };
285
294
  }
286
295
  /**
296
+ * ## MLS only ##
287
297
  * If there is a groupId in the conversation, we need to store the conversationId => groupId pair
288
298
  * in order to find the groupId when decrypting messages
289
299
  * This is a bit hacky but since mls messages do not embed the groupId we need to keep a mapping of those
@@ -297,6 +307,7 @@ class NotificationService extends events_1.EventEmitter {
297
307
  }
298
308
  }
299
309
  /**
310
+ * ## MLS only ##
300
311
  * If there is a matching conversationId => groupId pair in the database,
301
312
  * we can find the groupId and return it as a Uint8Array
302
313
  *
@@ -313,6 +324,84 @@ class NotificationService extends events_1.EventEmitter {
313
324
  }
314
325
  return bazinga64_1.Decoder.fromBase64(groupId).asBytes;
315
326
  }
327
+ /**
328
+ * ## MLS only ##
329
+ * If there are pending proposals, we need to either process them,
330
+ * or save them in the database for later processing
331
+ *
332
+ * @param groupId groupId of the mls conversation
333
+ * @param delayInMs delay in ms before processing proposals
334
+ * @param eventTime time of the event that had the proposals
335
+ */
336
+ async handlePendingProposals({ delayInMs, groupId, eventTime }) {
337
+ if (delayInMs > 0) {
338
+ const eventDate = new Date(eventTime);
339
+ const firingDate = eventDate.setTime(eventDate.getTime() + delayInMs);
340
+ try {
341
+ await this.database.storePendingProposal({
342
+ groupId,
343
+ firingDate,
344
+ });
345
+ }
346
+ catch (error) {
347
+ this.logger.error('Could not store pending proposal', error);
348
+ }
349
+ finally {
350
+ TaskScheduler_1.TaskScheduler.addTask({
351
+ task: () => this.commitPendingProposals({ groupId }),
352
+ firingDate,
353
+ key: groupId,
354
+ });
355
+ }
356
+ }
357
+ else {
358
+ await this.commitPendingProposals({ groupId, skipDelete: true });
359
+ }
360
+ }
361
+ /**
362
+ * ## MLS only ##
363
+ * Commit all pending proposals for a given groupId
364
+ *
365
+ * @param groupId groupId of the conversation
366
+ * @param skipDelete if true, do not delete the pending proposals from the database
367
+ */
368
+ async commitPendingProposals({ groupId, skipDelete = false }) {
369
+ const coreCryptoClient = this.coreCryptoClientProvider();
370
+ if (!coreCryptoClient) {
371
+ throw new Error('Could not get coreCryptoClient');
372
+ }
373
+ try {
374
+ await coreCryptoClient.commitPendingProposals(bazinga64_1.Decoder.fromBase64(groupId).asBytes);
375
+ if (!skipDelete) {
376
+ TaskScheduler_1.TaskScheduler.cancelTask(groupId);
377
+ await this.database.deletePendingProposal({ groupId });
378
+ }
379
+ }
380
+ catch (error) {
381
+ this.logger.error(`Error while committing pending proposals for groupId ${groupId}`, error);
382
+ }
383
+ }
384
+ /**
385
+ * ## MLS only ##
386
+ * Get all pending proposals from the database and schedule them
387
+ * Function must only be called once, after application start
388
+ *
389
+ */
390
+ async checkExistingPendingProposals() {
391
+ try {
392
+ const pendingProposals = await this.database.getStoredPendingProposals();
393
+ if (pendingProposals.length > 0) {
394
+ pendingProposals.forEach(({ groupId, firingDate }) => TaskScheduler_1.TaskScheduler.addTask({
395
+ task: () => this.commitPendingProposals({ groupId }),
396
+ firingDate,
397
+ key: groupId,
398
+ }));
399
+ }
400
+ }
401
+ catch (error) {
402
+ this.logger.error('Could not get pending proposals', error);
403
+ }
404
+ }
316
405
  }
317
406
  exports.NotificationService = NotificationService;
318
407
  NotificationService.TOPIC = TOPIC;
@@ -0,0 +1,17 @@
1
+ export declare type CommonMLS = {
2
+ groupId: string;
3
+ };
4
+ export declare type CompoundGroupIdParams = {
5
+ conversationId: string;
6
+ conversationDomain: string;
7
+ } & CommonMLS;
8
+ export declare type HandlePendingProposalsParams = {
9
+ delayInMs: number;
10
+ eventTime: string;
11
+ } & CommonMLS;
12
+ export declare type CommitPendingProposalsParams = {
13
+ skipDelete?: boolean;
14
+ } & CommonMLS;
15
+ export declare type StorePendingProposalsParams = {
16
+ firingDate: number;
17
+ } & CommonMLS;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ /*
3
+ * Wire
4
+ * Copyright (C) 2022 Wire Swiss GmbH
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program. If not, see http://www.gnu.org/licenses/.
18
+ *
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,10 @@
1
+ declare type ScheduleTaskParams = {
2
+ task: () => Promise<void>;
3
+ firingDate: number;
4
+ key: string;
5
+ };
6
+ export declare const TaskScheduler: {
7
+ addTask: ({ task, firingDate, key }: ScheduleTaskParams) => void;
8
+ cancelTask: (key: string) => void;
9
+ };
10
+ export {};
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /*
3
+ * Wire
4
+ * Copyright (C) 2022 Wire Swiss GmbH
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program. If not, see http://www.gnu.org/licenses/.
18
+ *
19
+ */
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.TaskScheduler = void 0;
25
+ const logdown_1 = __importDefault(require("logdown"));
26
+ const logger = (0, logdown_1.default)('@wireapp/core/util/TaskScheduler/TaskScheduler', {
27
+ logger: console,
28
+ markdown: false,
29
+ });
30
+ const activeTimeouts = {};
31
+ /**
32
+ * Execute a task at a given time.
33
+ *
34
+ * @param task function to be executed
35
+ * @param firingDate execution date
36
+ * @param key unique key for the task
37
+ */
38
+ const addTask = ({ task, firingDate, key }) => {
39
+ const now = new Date();
40
+ const execute = new Date(firingDate);
41
+ const delay = execute.getTime() - now.getTime();
42
+ if (activeTimeouts[key]) {
43
+ cancelTask(key);
44
+ }
45
+ const timeout = setTimeout(async () => {
46
+ await task();
47
+ delete activeTimeouts[key];
48
+ }, delay > 0 ? delay : 0);
49
+ // add the task to the list of active tasks
50
+ activeTimeouts[key] = timeout;
51
+ logger.info(`New scheduled task to be executed at "${execute}" with key "${key}"`);
52
+ };
53
+ /**
54
+ * Cancel a scheduled task early
55
+ *
56
+ * @param key unique key for the task
57
+ */
58
+ const cancelTask = (key) => {
59
+ const timeout = activeTimeouts[key];
60
+ if (timeout) {
61
+ clearTimeout(timeout);
62
+ delete activeTimeouts[key];
63
+ logger.info(`Scheduled task with key "${key}" prematurely cleared`);
64
+ }
65
+ };
66
+ exports.TaskScheduler = {
67
+ addTask,
68
+ cancelTask,
69
+ };
70
+ //# sourceMappingURL=TaskScheduler.js.map
@@ -0,0 +1,8 @@
1
+ import { QualifiedUserClientMap } from '@wireapp/api-client/src/client';
2
+ declare type UserId = string;
3
+ declare type ClientId = string;
4
+ declare type Domain = string;
5
+ export declare type ClientIdStringType = `${UserId}:${ClientId}@${Domain}`;
6
+ export declare const constructFullyQualifiedClientId: (userId: UserId, clientId: ClientId, domain: Domain) => ClientIdStringType;
7
+ export declare const mapQualifiedUserClientIdsToFullyQualifiedClientIds: (qualifiedUserMap: QualifiedUserClientMap) => Uint8Array[];
8
+ export {};
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ /*
3
+ * Wire
4
+ * Copyright (C) 2022 Wire Swiss GmbH
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program. If not, see http://www.gnu.org/licenses/.
18
+ *
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.mapQualifiedUserClientIdsToFullyQualifiedClientIds = exports.constructFullyQualifiedClientId = void 0;
22
+ const constructFullyQualifiedClientId = (userId, clientId, domain) => `${userId}:${clientId}@${domain}`;
23
+ exports.constructFullyQualifiedClientId = constructFullyQualifiedClientId;
24
+ const mapQualifiedUserClientIdsToFullyQualifiedClientIds = (qualifiedUserMap) => {
25
+ const encoder = new TextEncoder();
26
+ return Object.entries(qualifiedUserMap).flatMap(([domain, users]) => {
27
+ const clients = Object.entries(users);
28
+ return clients.flatMap(([userId, clients]) => clients.map(client => encoder.encode((0, exports.constructFullyQualifiedClientId)(userId, client.id, domain))));
29
+ });
30
+ };
31
+ exports.mapQualifiedUserClientIdsToFullyQualifiedClientIds = mapQualifiedUserClientIdsToFullyQualifiedClientIds;
32
+ //# sourceMappingURL=mapQualifiedUserClientIdsToFullyQualifiedClientIds.js.map