@wireapp/core 29.2.2 → 29.3.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.
- package/CHANGELOG.md +27 -0
- package/package.json +3 -3
- package/src/main/Account.js +4 -0
- package/src/main/conversation/ConversationService/ConversationService.d.ts +1 -1
- package/src/main/conversation/ConversationService/ConversationService.js +5 -1
- package/src/main/conversation/content/AssetContent.d.ts +3 -3
- package/src/main/conversation/content/ContentType.d.ts +2 -2
- package/src/main/conversation/content/ConversationContent.d.ts +2 -2
- package/src/main/conversation/content/EditedTextContent.d.ts +1 -1
- package/src/main/conversation/content/LinkPreviewContent.d.ts +1 -1
- package/src/main/conversation/content/LocationContent.d.ts +1 -1
- package/src/main/conversation/content/QuoteContent.d.ts +1 -1
- package/src/main/conversation/content/ReactionContent.d.ts +1 -1
- package/src/main/conversation/content/TextContent.d.ts +1 -1
- package/src/main/cryptography/CryptographyDatabaseRepository.d.ts +2 -1
- package/src/main/cryptography/CryptographyDatabaseRepository.js +2 -8
- package/src/main/notification/NotificationDatabaseRepository.d.ts +23 -7
- package/src/main/notification/NotificationDatabaseRepository.js +42 -9
- package/src/main/notification/NotificationService.d.ts +28 -0
- package/src/main/notification/NotificationService.js +95 -6
- package/src/main/notification/types.d.ts +17 -0
- package/src/main/notification/types.js +21 -0
- package/src/main/util/TaskScheduler/TaskScheduler.d.ts +10 -0
- package/src/main/util/TaskScheduler/TaskScheduler.js +70 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,33 @@
|
|
|
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
|
+
# [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)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* 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))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [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)
|
|
18
|
+
|
|
19
|
+
**Note:** Version bump only for package @wireapp/core
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## [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)
|
|
26
|
+
|
|
27
|
+
**Note:** Version bump only for package @wireapp/core
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
6
33
|
## [29.2.2](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@29.2.1...@wireapp/core@29.2.2) (2022-08-17)
|
|
7
34
|
|
|
8
35
|
**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.
|
|
10
|
+
"@wireapp/api-client": "19.21.6",
|
|
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.
|
|
81
|
-
"gitHead": "
|
|
80
|
+
"version": "29.3.0",
|
|
81
|
+
"gitHead": "e9707c97b3cfa69fa1e51c21d0d0ca6923f57d4d"
|
|
82
82
|
}
|
package/src/main/Account.js
CHANGED
|
@@ -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
|
}
|
|
@@ -4,7 +4,7 @@ import { MessageSendingStatus, Conversation, DefaultConversationRoleName, MutedS
|
|
|
4
4
|
import type { ConversationMemberLeaveEvent } from '@wireapp/api-client/src/event';
|
|
5
5
|
import type { QualifiedId, UserPreKeyBundleMap } from '@wireapp/api-client/src/user';
|
|
6
6
|
import { MessageTimer } from '../../conversation/';
|
|
7
|
-
import type { RemoteData } from '
|
|
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';
|
|
@@ -839,6 +839,9 @@ class ConversationService {
|
|
|
839
839
|
*/
|
|
840
840
|
const newConversation = await this.apiClient.api.conversation.postConversation(Object.assign(Object.assign({}, conversationData), { users: undefined, qualified_users: undefined }));
|
|
841
841
|
const { group_id: groupId, qualified_id: qualifiedId } = newConversation;
|
|
842
|
+
if (!groupId) {
|
|
843
|
+
throw new Error('No group_id found in response which is required for creating MLS conversations.');
|
|
844
|
+
}
|
|
842
845
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
843
846
|
const { qualified_users: qualifiedUsers = [], selfUserId } = conversationData;
|
|
844
847
|
if (!selfUserId) {
|
|
@@ -871,6 +874,8 @@ class ConversationService {
|
|
|
871
874
|
var _a, _b;
|
|
872
875
|
const { groupId, onSuccess, payload } = params;
|
|
873
876
|
const groupIdBytes = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
877
|
+
// immediately execute pending commits before sending the message
|
|
878
|
+
await this.notificationService.commitPendingProposals({ groupId });
|
|
874
879
|
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
875
880
|
const encrypted = await coreCryptoClient.encryptMessage(groupIdBytes, protocol_messaging_1.GenericMessage.encode(genericMessage).finish());
|
|
876
881
|
try {
|
|
@@ -886,7 +891,6 @@ class ConversationService {
|
|
|
886
891
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
887
892
|
const coreCryptoKeyPackagesPayload = await this.getCoreCryptoKeyPackagesPayload([...qualifiedUserIds]);
|
|
888
893
|
const response = await this.addUsersToExistingMLSConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
|
|
889
|
-
console.info('addUsersToMLSGroup', conversationId, qualifiedUserIds, groupIdDecodedFromBase64, response);
|
|
890
894
|
const conversation = await this.getConversations(conversationId.id);
|
|
891
895
|
return {
|
|
892
896
|
events: (response === null || response === void 0 ? void 0 : response.events) || [],
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { Asset } from '@wireapp/protocol-messaging';
|
|
3
|
-
import type { AbortReason, AssetTransferState } from '
|
|
4
|
-
import type { FileContent, FileMetaDataContent, ImageContent, LegalHoldStatus } from '
|
|
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 '
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import type { ILinkPreview } from '@wireapp/protocol-messaging';
|
|
2
|
-
import type { ImageAssetContent, ImageContent, LegalHoldStatus } from '
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import type { IQuote } from '@wireapp/protocol-messaging';
|
|
2
|
-
import type { AssetContent, LocationContent, TextContent } from '
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import type { LegalHoldStatus, LinkPreviewUploadedContent, MentionContent, QuoteContent } from '
|
|
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
|
-
|
|
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<
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
231
|
-
|
|
232
|
-
|
|
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(
|
|
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((
|
|
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
|