@wireapp/core 27.0.5 → 27.1.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 +11 -0
- package/package.json +2 -2
- package/src/main/Account.d.ts +34 -5
- package/src/main/Account.js +63 -27
- package/src/main/cryptography/CryptographyService.d.ts +8 -1
- package/src/main/cryptography/CryptographyService.js +35 -3
- package/src/main/notification/NotificationService.d.ts +18 -31
- package/src/main/notification/NotificationService.js +77 -79
- package/src/main/user/UserMapper.d.ts +1 -1
- package/src/main/user/UserMapper.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
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
|
+
# [27.1.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.0.5...@wireapp/core@27.1.0) (2022-05-30)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Improve websocket connection API ([#4268](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4268)) ([a6eb884](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/a6eb884ddf42915157c8d6c846b0a8a989c3fc04))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
## [27.0.5](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.0.4...@wireapp/core@27.0.5) (2022-05-30)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @wireapp/core
|
package/package.json
CHANGED
|
@@ -69,6 +69,6 @@
|
|
|
69
69
|
"test:project": "yarn dist && yarn test",
|
|
70
70
|
"test:node": "nyc jasmine --config=jasmine.json"
|
|
71
71
|
},
|
|
72
|
-
"version": "27.0
|
|
73
|
-
"gitHead": "
|
|
72
|
+
"version": "27.1.0",
|
|
73
|
+
"gitHead": "fbbc85c85cd536d34c2e32a3fa63ae649dfd559e"
|
|
74
74
|
}
|
package/src/main/Account.d.ts
CHANGED
|
@@ -9,18 +9,20 @@ import { EventEmitter } from 'events';
|
|
|
9
9
|
import { BroadcastService } from './broadcast/';
|
|
10
10
|
import { ClientInfo, ClientService } from './client/';
|
|
11
11
|
import { ConnectionService } from './connection/';
|
|
12
|
-
import { AssetService, ConversationService, PayloadBundleType } from './conversation/';
|
|
12
|
+
import { AssetService, ConversationService, PayloadBundleSource, PayloadBundleType } from './conversation/';
|
|
13
13
|
import * as OtrMessage from './conversation/message/OtrMessage';
|
|
14
14
|
import * as UserMessage from './conversation/message/UserMessage';
|
|
15
15
|
import type { CoreError } from './CoreError';
|
|
16
16
|
import { CryptographyService } from './cryptography/';
|
|
17
17
|
import { GiphyService } from './giphy/';
|
|
18
|
-
import {
|
|
18
|
+
import { HandledEventPayload, NotificationService } from './notification/';
|
|
19
19
|
import { SelfService } from './self/';
|
|
20
20
|
import { TeamService } from './team/';
|
|
21
21
|
import { UserService } from './user/';
|
|
22
22
|
import { AccountService } from './account/';
|
|
23
23
|
import { LinkPreviewService } from './linkPreview';
|
|
24
|
+
import { WEBSOCKET_STATE } from '@wireapp/api-client/src/tcp/ReconnectingWebsocket';
|
|
25
|
+
export declare type ProcessedEventPayload = HandledEventPayload;
|
|
24
26
|
declare enum TOPIC {
|
|
25
27
|
ERROR = "Account.TOPIC.ERROR"
|
|
26
28
|
}
|
|
@@ -141,9 +143,36 @@ export declare class Account extends EventEmitter {
|
|
|
141
143
|
private registerClient;
|
|
142
144
|
private resetContext;
|
|
143
145
|
logout(): Promise<void>;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Will download and handle the notification stream since last stored notification id.
|
|
148
|
+
* Once the notification stream has been handled from backend, will then connect to the websocket and start listening to incoming events
|
|
149
|
+
*
|
|
150
|
+
* @param callbacks callbacks that will be called to handle different events
|
|
151
|
+
* @returns close a function that will disconnect from the websocket
|
|
152
|
+
*/
|
|
153
|
+
listen({ onEvent, onConnected, onConnectionStateChanged, onNotificationStreamProgress, }?: {
|
|
154
|
+
/**
|
|
155
|
+
* Called when a new event arrives from backend
|
|
156
|
+
* @param payload the payload of the event. Contains the raw event received and the decrypted data (if event was encrypted)
|
|
157
|
+
* @param source where the message comes from (either websocket or notification stream)
|
|
158
|
+
*/
|
|
159
|
+
onEvent?: (payload: HandledEventPayload, source: PayloadBundleSource) => void;
|
|
160
|
+
/**
|
|
161
|
+
* During the notification stream processing, this function will be called whenever a new notification has been processed
|
|
162
|
+
*/
|
|
163
|
+
onNotificationStreamProgress?: ({ done, total }: {
|
|
164
|
+
done: number;
|
|
165
|
+
total: number;
|
|
166
|
+
}) => void;
|
|
167
|
+
/**
|
|
168
|
+
* called when the connection to the websocket is established and the notification stream has been processed
|
|
169
|
+
*/
|
|
170
|
+
onConnected?: () => void;
|
|
171
|
+
/**
|
|
172
|
+
* called when the connection stateh with the backend has changed
|
|
173
|
+
*/
|
|
174
|
+
onConnectionStateChanged?: (state: WEBSOCKET_STATE) => void;
|
|
175
|
+
}): Promise<() => void>;
|
|
147
176
|
private initEngine;
|
|
148
177
|
}
|
|
149
178
|
export {};
|
package/src/main/Account.js
CHANGED
|
@@ -36,6 +36,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
36
36
|
__setModuleDefault(result, mod);
|
|
37
37
|
return result;
|
|
38
38
|
};
|
|
39
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
40
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
41
|
+
var m = o[Symbol.asyncIterator], i;
|
|
42
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
43
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
44
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
45
|
+
};
|
|
39
46
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
47
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
48
|
};
|
|
@@ -79,20 +86,6 @@ class Account extends events_1.EventEmitter {
|
|
|
79
86
|
*/
|
|
80
87
|
constructor(apiClient = new api_client_1.APIClient(), { createStore = () => undefined, nbPrekeys = 2 } = {}) {
|
|
81
88
|
super();
|
|
82
|
-
this.handlePayload = async (payload) => {
|
|
83
|
-
switch (payload.type) {
|
|
84
|
-
case conversation_1.PayloadBundleType.TIMER_UPDATE: {
|
|
85
|
-
const { data: { message_timer }, conversation, } = payload;
|
|
86
|
-
const expireAfterMillis = Number(message_timer);
|
|
87
|
-
this.service.conversation.messageTimer.setConversationLevelTimer(conversation, expireAfterMillis);
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
this.emit(payload.type, payload);
|
|
92
|
-
};
|
|
93
|
-
this.handleError = (accountError) => {
|
|
94
|
-
this.emit(Account.TOPIC.ERROR, accountError);
|
|
95
|
-
};
|
|
96
89
|
this.apiClient = apiClient;
|
|
97
90
|
this.backendFeatures = this.apiClient.backendFeatures;
|
|
98
91
|
this.nbPrekeys = nbPrekeys;
|
|
@@ -284,25 +277,68 @@ class Account extends events_1.EventEmitter {
|
|
|
284
277
|
await this.apiClient.logout();
|
|
285
278
|
this.resetContext();
|
|
286
279
|
}
|
|
287
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Will download and handle the notification stream since last stored notification id.
|
|
282
|
+
* Once the notification stream has been handled from backend, will then connect to the websocket and start listening to incoming events
|
|
283
|
+
*
|
|
284
|
+
* @param callbacks callbacks that will be called to handle different events
|
|
285
|
+
* @returns close a function that will disconnect from the websocket
|
|
286
|
+
*/
|
|
287
|
+
async listen({ onEvent = () => { }, onConnected = () => { }, onConnectionStateChanged = () => { }, onNotificationStreamProgress = () => { }, } = {}) {
|
|
288
288
|
if (!this.apiClient.context) {
|
|
289
289
|
throw new Error('Context is not set - please login first');
|
|
290
290
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
const handleEvent = async (payload, source) => {
|
|
292
|
+
const { mappedEvent } = payload;
|
|
293
|
+
switch (mappedEvent === null || mappedEvent === void 0 ? void 0 : mappedEvent.type) {
|
|
294
|
+
case conversation_1.PayloadBundleType.TIMER_UPDATE: {
|
|
295
|
+
const { data: { message_timer }, conversation, } = payload.event;
|
|
296
|
+
const expireAfterMillis = Number(message_timer);
|
|
297
|
+
this.service.conversation.messageTimer.setConversationLevelTimer(conversation, expireAfterMillis);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
onEvent(payload, source);
|
|
302
|
+
if (mappedEvent) {
|
|
303
|
+
this.emit(mappedEvent.type, payload.mappedEvent);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const handleNotification = async (notification, source) => {
|
|
307
|
+
var e_1, _a;
|
|
308
|
+
try {
|
|
309
|
+
const messages = this.service.notification.handleNotification(notification, conversation_1.PayloadBundleSource.WEBSOCKET);
|
|
310
|
+
try {
|
|
311
|
+
for (var messages_1 = __asyncValues(messages), messages_1_1; messages_1_1 = await messages_1.next(), !messages_1_1.done;) {
|
|
312
|
+
const message = messages_1_1.value;
|
|
313
|
+
await handleEvent(message, source);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
317
|
+
finally {
|
|
318
|
+
try {
|
|
319
|
+
if (messages_1_1 && !messages_1_1.done && (_a = messages_1.return)) await _a.call(messages_1);
|
|
320
|
+
}
|
|
321
|
+
finally { if (e_1) throw e_1.error; }
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
294
325
|
this.logger.error(`Failed to handle notification ID "${notification.id}": ${error.message}`, error);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
this.apiClient.transport.ws.removeAllListeners(tcp_1.WebSocketClient.TOPIC.ON_MESSAGE);
|
|
329
|
+
this.apiClient.transport.ws.on(tcp_1.WebSocketClient.TOPIC.ON_MESSAGE, notification => handleNotification(notification, conversation_1.PayloadBundleSource.WEBSOCKET));
|
|
330
|
+
this.apiClient.transport.ws.on(tcp_1.WebSocketClient.TOPIC.ON_STATE_CHANGE, onConnectionStateChanged);
|
|
331
|
+
const onBeforeConnect = async () => {
|
|
332
|
+
await this.service.notification.handleNotificationStream(async (notification, source, progress) => {
|
|
333
|
+
await handleNotification(notification, source);
|
|
334
|
+
onNotificationStreamProgress(progress);
|
|
295
335
|
});
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
this.service.notification.on(notification_1.NotificationService.TOPIC.NOTIFICATION_ERROR, this.handleError);
|
|
299
|
-
for (const payloadType of Object.values(conversation_1.PayloadBundleType)) {
|
|
300
|
-
this.service.notification.removeAllListeners(payloadType);
|
|
301
|
-
this.service.notification.on(payloadType, this.handlePayload);
|
|
302
|
-
}
|
|
303
|
-
const onBeforeConnect = async () => this.service.notification.handleNotificationStream(notificationHandler);
|
|
336
|
+
onConnected();
|
|
337
|
+
};
|
|
304
338
|
await this.apiClient.connect(onBeforeConnect);
|
|
305
|
-
return
|
|
339
|
+
return () => {
|
|
340
|
+
this.apiClient.disconnect();
|
|
341
|
+
};
|
|
306
342
|
}
|
|
307
343
|
async initEngine(context) {
|
|
308
344
|
const clientType = context.clientType === client_1.ClientType.NONE ? '' : `@${context.clientType}`;
|
|
@@ -5,8 +5,13 @@ import type { OTRRecipients, QualifiedOTRRecipients, QualifiedUserClients, UserC
|
|
|
5
5
|
import type { ConversationOtrMessageAddEvent } from '@wireapp/api-client/src/event';
|
|
6
6
|
import type { QualifiedId, QualifiedUserPreKeyBundleMap, UserPreKeyBundleMap } from '@wireapp/api-client/src/user/';
|
|
7
7
|
import { Cryptobox } from '@wireapp/cryptobox';
|
|
8
|
+
import { GenericMessage } from '@wireapp/protocol-messaging';
|
|
8
9
|
import type { CRUDEngine } from '@wireapp/store-engine';
|
|
9
10
|
import { PayloadBundle, PayloadBundleSource } from '../conversation';
|
|
11
|
+
export declare type DecryptionError = {
|
|
12
|
+
code: number;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
10
15
|
export interface MetaClient extends RegisteredClient {
|
|
11
16
|
meta: {
|
|
12
17
|
is_verified?: boolean;
|
|
@@ -41,5 +46,7 @@ export declare class CryptographyService {
|
|
|
41
46
|
initCryptobox(): Promise<void>;
|
|
42
47
|
deleteCryptographyStores(): Promise<boolean[]>;
|
|
43
48
|
resetSession(sessionId: string): Promise<void>;
|
|
44
|
-
|
|
49
|
+
decryptMessage(otrMessage: ConversationOtrMessageAddEvent): Promise<GenericMessage>;
|
|
50
|
+
mapGenericMessage(otrMessage: ConversationOtrMessageAddEvent, genericMessage: GenericMessage, source: PayloadBundleSource): PayloadBundle;
|
|
51
|
+
private generateDecryptionError;
|
|
45
52
|
}
|
|
@@ -151,11 +151,18 @@ class CryptographyService {
|
|
|
151
151
|
await this.cryptobox.session_delete(sessionId);
|
|
152
152
|
this.logger.log(`Deleted session ID "${sessionId}".`);
|
|
153
153
|
}
|
|
154
|
-
async
|
|
154
|
+
async decryptMessage(otrMessage) {
|
|
155
155
|
const { from, qualified_from, data: { sender, text: cipherText }, } = otrMessage;
|
|
156
156
|
const sessionId = this.constructSessionId(from, sender, qualified_from === null || qualified_from === void 0 ? void 0 : qualified_from.domain);
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
try {
|
|
158
|
+
const decryptedMessage = await this.decrypt(sessionId, cipherText);
|
|
159
|
+
return protocol_messaging_1.GenericMessage.decode(decryptedMessage);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
throw this.generateDecryptionError(otrMessage, error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
mapGenericMessage(otrMessage, genericMessage, source) {
|
|
159
166
|
if (genericMessage.content === conversation_1.GenericMessageType.EPHEMERAL) {
|
|
160
167
|
const unwrappedMessage = GenericMessageMapper_1.GenericMessageMapper.mapGenericMessage(genericMessage.ephemeral, otrMessage, source);
|
|
161
168
|
unwrappedMessage.id = genericMessage.messageId;
|
|
@@ -168,6 +175,31 @@ class CryptographyService {
|
|
|
168
175
|
}
|
|
169
176
|
return GenericMessageMapper_1.GenericMessageMapper.mapGenericMessage(genericMessage, otrMessage, source);
|
|
170
177
|
}
|
|
178
|
+
generateDecryptionError(event, error) {
|
|
179
|
+
var _a;
|
|
180
|
+
const errorCode = (_a = error.code) !== null && _a !== void 0 ? _a : 999;
|
|
181
|
+
let message = 'Unknown decryption error';
|
|
182
|
+
const { data: eventData, from: remoteUserId, time: formattedTime } = event;
|
|
183
|
+
const remoteClientId = eventData.sender;
|
|
184
|
+
const isDuplicateMessage = error instanceof proteus_1.errors.DecryptError.DuplicateMessage;
|
|
185
|
+
const isOutdatedMessage = error instanceof proteus_1.errors.DecryptError.OutdatedMessage;
|
|
186
|
+
// We don't need to show these message errors to the user
|
|
187
|
+
if (isDuplicateMessage || isOutdatedMessage) {
|
|
188
|
+
message = `Message from user ID "${remoteUserId}" at "${formattedTime}" will not be handled because it is outdated or a duplicate.`;
|
|
189
|
+
}
|
|
190
|
+
const isInvalidMessage = error instanceof proteus_1.errors.DecryptError.InvalidMessage;
|
|
191
|
+
const isInvalidSignature = error instanceof proteus_1.errors.DecryptError.InvalidSignature;
|
|
192
|
+
const isRemoteIdentityChanged = error instanceof proteus_1.errors.DecryptError.RemoteIdentityChanged;
|
|
193
|
+
// Session is broken, let's see what's really causing it...
|
|
194
|
+
if (isInvalidMessage || isInvalidSignature) {
|
|
195
|
+
message = `Session with user '${remoteUserId}' (${remoteClientId}) is broken.\nReset the session for possible fix.`;
|
|
196
|
+
}
|
|
197
|
+
else if (isRemoteIdentityChanged) {
|
|
198
|
+
message = `Remote identity of client '${remoteClientId}' from user '${remoteUserId}' changed`;
|
|
199
|
+
}
|
|
200
|
+
this.logger.warn(`Failed to decrypt event from client '${remoteClientId}' of user '${remoteUserId}' (${formattedTime}).\nError Code: '${errorCode}'\nError Message: ${error.message}`, error);
|
|
201
|
+
return { code: errorCode, message };
|
|
202
|
+
}
|
|
171
203
|
}
|
|
172
204
|
exports.CryptographyService = CryptographyService;
|
|
173
205
|
//# sourceMappingURL=CryptographyService.js.map
|
|
@@ -4,41 +4,27 @@ import * as Events from '@wireapp/api-client/src/event';
|
|
|
4
4
|
import type { Notification } from '@wireapp/api-client/src/notification/';
|
|
5
5
|
import { CRUDEngine } from '@wireapp/store-engine';
|
|
6
6
|
import { EventEmitter } from 'events';
|
|
7
|
-
import {
|
|
8
|
-
import * as OtrMessage from '../conversation/message/OtrMessage';
|
|
9
|
-
import * as UserMessage from '../conversation/message/UserMessage';
|
|
7
|
+
import { PayloadBundle, PayloadBundleSource } from '../conversation';
|
|
10
8
|
import { NotificationError } from '../CoreError';
|
|
11
9
|
import type { CryptographyService } from '../cryptography';
|
|
10
|
+
import { GenericMessage } from '@wireapp/protocol-messaging';
|
|
11
|
+
export declare type HandledEventPayload = {
|
|
12
|
+
event: Events.BackendEvent;
|
|
13
|
+
mappedEvent?: PayloadBundle;
|
|
14
|
+
decryptedData?: GenericMessage;
|
|
15
|
+
decryptionError?: {
|
|
16
|
+
code: number;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
12
20
|
declare enum TOPIC {
|
|
13
21
|
NOTIFICATION_ERROR = "NotificationService.TOPIC.NOTIFICATION_ERROR"
|
|
14
22
|
}
|
|
15
|
-
export declare type NotificationHandler = (notification: Notification, source: PayloadBundleSource
|
|
23
|
+
export declare type NotificationHandler = (notification: Notification, source: PayloadBundleSource, progress: {
|
|
24
|
+
done: number;
|
|
25
|
+
total: number;
|
|
26
|
+
}) => Promise<void>;
|
|
16
27
|
export interface NotificationService {
|
|
17
|
-
on(event: PayloadBundleType.ASSET, listener: (payload: OtrMessage.FileAssetMessage) => void): this;
|
|
18
|
-
on(event: PayloadBundleType.BUTTON_ACTION, listener: (payload: OtrMessage.ButtonActionMessage) => void): this;
|
|
19
|
-
on(event: PayloadBundleType.ASSET_ABORT, listener: (payload: OtrMessage.FileAssetAbortMessage) => void): this;
|
|
20
|
-
on(event: PayloadBundleType.ASSET_IMAGE, listener: (payload: OtrMessage.ImageAssetMessage) => void): this;
|
|
21
|
-
on(event: PayloadBundleType.ASSET_META, listener: (payload: OtrMessage.FileAssetMetaDataMessage) => void): this;
|
|
22
|
-
on(event: PayloadBundleType.CALL, listener: (payload: OtrMessage.CallMessage) => void): this;
|
|
23
|
-
on(event: PayloadBundleType.CLIENT_ACTION, listener: (payload: OtrMessage.ResetSessionMessage) => void): this;
|
|
24
|
-
on(event: PayloadBundleType.CLIENT_ADD, listener: (payload: UserMessage.UserClientAddMessage) => void): this;
|
|
25
|
-
on(event: PayloadBundleType.CLIENT_REMOVE, listener: (payload: UserMessage.UserClientRemoveMessage) => void): this;
|
|
26
|
-
on(event: PayloadBundleType.CONFIRMATION, listener: (payload: OtrMessage.ConfirmationMessage) => void): this;
|
|
27
|
-
on(event: PayloadBundleType.CONNECTION_REQUEST, listener: (payload: UserMessage.UserConnectionMessage) => void): this;
|
|
28
|
-
on(event: PayloadBundleType.USER_UPDATE, listener: (payload: UserMessage.UserUpdateMessage) => void): this;
|
|
29
|
-
on(event: PayloadBundleType.CONVERSATION_CLEAR, listener: (payload: OtrMessage.ClearConversationMessage) => void): this;
|
|
30
|
-
on(event: PayloadBundleType.CONVERSATION_RENAME, listener: (payload: Events.ConversationRenameEvent) => void): this;
|
|
31
|
-
on(event: PayloadBundleType.LOCATION, listener: (payload: OtrMessage.LocationMessage) => void): this;
|
|
32
|
-
on(event: PayloadBundleType.MEMBER_JOIN, listener: (payload: Events.TeamMemberJoinEvent) => void): this;
|
|
33
|
-
on(event: PayloadBundleType.MESSAGE_DELETE, listener: (payload: OtrMessage.DeleteMessage) => void): this;
|
|
34
|
-
on(event: PayloadBundleType.MESSAGE_EDIT, listener: (payload: OtrMessage.EditedTextMessage) => void): this;
|
|
35
|
-
on(event: PayloadBundleType.MESSAGE_HIDE, listener: (payload: OtrMessage.HideMessage) => void): this;
|
|
36
|
-
on(event: PayloadBundleType.PING, listener: (payload: OtrMessage.PingMessage) => void): this;
|
|
37
|
-
on(event: PayloadBundleType.REACTION, listener: (payload: OtrMessage.ReactionMessage) => void): this;
|
|
38
|
-
on(event: PayloadBundleType.TEXT, listener: (payload: OtrMessage.TextMessage) => void): this;
|
|
39
|
-
on(event: PayloadBundleType.TIMER_UPDATE, listener: (payload: Events.ConversationMessageTimerUpdateEvent) => void): this;
|
|
40
|
-
on(event: PayloadBundleType.TYPING, listener: (payload: Events.ConversationTypingEvent) => void): this;
|
|
41
|
-
on(event: PayloadBundleType.UNKNOWN, listener: (payload: any) => void): this;
|
|
42
28
|
on(event: TOPIC.NOTIFICATION_ERROR, listener: (payload: NotificationError) => void): this;
|
|
43
29
|
}
|
|
44
30
|
export declare class NotificationService extends EventEmitter {
|
|
@@ -49,7 +35,7 @@ export declare class NotificationService extends EventEmitter {
|
|
|
49
35
|
private readonly logger;
|
|
50
36
|
static readonly TOPIC: typeof TOPIC;
|
|
51
37
|
constructor(apiClient: APIClient, cryptographyService: CryptographyService, storeEngine: CRUDEngine);
|
|
52
|
-
getAllNotifications
|
|
38
|
+
private getAllNotifications;
|
|
53
39
|
/** Should only be called with a completely new client. */
|
|
54
40
|
initializeNotificationStream(): Promise<string>;
|
|
55
41
|
hasHistory(): Promise<boolean>;
|
|
@@ -57,7 +43,8 @@ export declare class NotificationService extends EventEmitter {
|
|
|
57
43
|
setLastEventDate(eventDate: Date): Promise<Date>;
|
|
58
44
|
setLastNotificationId(lastNotification: Notification): Promise<string>;
|
|
59
45
|
handleNotificationStream(notificationHandler: NotificationHandler): Promise<void>;
|
|
60
|
-
|
|
46
|
+
handleNotification(notification: Notification, source: PayloadBundleSource): AsyncGenerator<HandledEventPayload>;
|
|
47
|
+
private cleanupPayloadBundle;
|
|
61
48
|
private handleEvent;
|
|
62
49
|
}
|
|
63
50
|
export {};
|
|
@@ -36,6 +36,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
36
36
|
__setModuleDefault(result, mod);
|
|
37
37
|
return result;
|
|
38
38
|
};
|
|
39
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
40
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
41
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
42
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
43
|
+
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
44
|
+
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
|
45
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
46
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
47
|
+
function fulfill(value) { resume("next", value); }
|
|
48
|
+
function reject(value) { resume("throw", value); }
|
|
49
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
50
|
+
};
|
|
39
51
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
52
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
53
|
};
|
|
@@ -62,80 +74,6 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
62
74
|
logger: console,
|
|
63
75
|
markdown: false,
|
|
64
76
|
});
|
|
65
|
-
this.handleNotification = async (notification, source) => {
|
|
66
|
-
for (const event of notification.payload) {
|
|
67
|
-
let data;
|
|
68
|
-
try {
|
|
69
|
-
this.logger.log(`Handling event of type "${event.type}" for notification with ID "${notification.id}"`, event);
|
|
70
|
-
data = await this.handleEvent(event, source);
|
|
71
|
-
if (!notification.transient) {
|
|
72
|
-
await this.setLastNotificationId(notification);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
this.logger.error(`There was an error with notification ID "${notification.id}": ${error.message}`, error);
|
|
77
|
-
const notificationError = {
|
|
78
|
-
error: error,
|
|
79
|
-
notification,
|
|
80
|
-
type: CoreError_1.CoreError.NOTIFICATION_ERROR,
|
|
81
|
-
};
|
|
82
|
-
this.emit(NotificationService.TOPIC.NOTIFICATION_ERROR, notificationError);
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (data) {
|
|
86
|
-
switch (data.type) {
|
|
87
|
-
case conversation_1.PayloadBundleType.ASSET_IMAGE:
|
|
88
|
-
case conversation_1.PayloadBundleType.BUTTON_ACTION:
|
|
89
|
-
case conversation_1.PayloadBundleType.CALL:
|
|
90
|
-
case conversation_1.PayloadBundleType.CLIENT_ACTION:
|
|
91
|
-
case conversation_1.PayloadBundleType.CLIENT_ADD:
|
|
92
|
-
case conversation_1.PayloadBundleType.CLIENT_REMOVE:
|
|
93
|
-
case conversation_1.PayloadBundleType.CONFIRMATION:
|
|
94
|
-
case conversation_1.PayloadBundleType.CONNECTION_REQUEST:
|
|
95
|
-
case conversation_1.PayloadBundleType.LOCATION:
|
|
96
|
-
case conversation_1.PayloadBundleType.MESSAGE_DELETE:
|
|
97
|
-
case conversation_1.PayloadBundleType.MESSAGE_EDIT:
|
|
98
|
-
case conversation_1.PayloadBundleType.MESSAGE_HIDE:
|
|
99
|
-
case conversation_1.PayloadBundleType.PING:
|
|
100
|
-
case conversation_1.PayloadBundleType.REACTION:
|
|
101
|
-
case conversation_1.PayloadBundleType.TEXT:
|
|
102
|
-
case conversation_1.PayloadBundleType.USER_UPDATE:
|
|
103
|
-
this.emit(data.type, data);
|
|
104
|
-
break;
|
|
105
|
-
case conversation_1.PayloadBundleType.ASSET: {
|
|
106
|
-
const assetContent = data.content;
|
|
107
|
-
const isMetaData = !!assetContent && !!assetContent.original && !assetContent.uploaded;
|
|
108
|
-
const isAbort = !!assetContent.abortReason || (!assetContent.original && !assetContent.uploaded);
|
|
109
|
-
if (isMetaData) {
|
|
110
|
-
data.type = conversation_1.PayloadBundleType.ASSET_META;
|
|
111
|
-
this.emit(conversation_1.PayloadBundleType.ASSET_META, data);
|
|
112
|
-
}
|
|
113
|
-
else if (isAbort) {
|
|
114
|
-
data.type = conversation_1.PayloadBundleType.ASSET_ABORT;
|
|
115
|
-
this.emit(conversation_1.PayloadBundleType.ASSET_ABORT, data);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
this.emit(conversation_1.PayloadBundleType.ASSET, data);
|
|
119
|
-
}
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
case conversation_1.PayloadBundleType.TIMER_UPDATE:
|
|
123
|
-
case conversation_1.PayloadBundleType.CONVERSATION_RENAME:
|
|
124
|
-
case conversation_1.PayloadBundleType.CONVERSATION_CLEAR:
|
|
125
|
-
case conversation_1.PayloadBundleType.MEMBER_JOIN:
|
|
126
|
-
case conversation_1.PayloadBundleType.TYPING:
|
|
127
|
-
this.emit(data.type, event);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
const { type, conversation, from } = event;
|
|
133
|
-
const conversationText = conversation ? ` in conversation "${conversation}"` : '';
|
|
134
|
-
const fromText = from ? ` from user "${from}".` : '';
|
|
135
|
-
this.logger.log(`Received unsupported event "${type}"${conversationText}${fromText}`, { event });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
77
|
this.apiClient = apiClient;
|
|
140
78
|
this.cryptographyService = cryptographyService;
|
|
141
79
|
this.backend = new NotificationBackendRepository_1.NotificationBackendRepository(this.apiClient);
|
|
@@ -182,15 +120,74 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
182
120
|
}
|
|
183
121
|
async handleNotificationStream(notificationHandler) {
|
|
184
122
|
const notifications = await this.getAllNotifications();
|
|
185
|
-
for (const notification of notifications) {
|
|
186
|
-
await notificationHandler(notification, conversation_1.PayloadBundleSource.NOTIFICATION_STREAM
|
|
123
|
+
for (const [index, notification] of notifications.entries()) {
|
|
124
|
+
await notificationHandler(notification, conversation_1.PayloadBundleSource.NOTIFICATION_STREAM, {
|
|
125
|
+
done: index + 1,
|
|
126
|
+
total: notifications.length,
|
|
127
|
+
}).catch(error => this.logger.error(error));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
handleNotification(notification, source) {
|
|
131
|
+
return __asyncGenerator(this, arguments, function* handleNotification_1() {
|
|
132
|
+
for (const event of notification.payload) {
|
|
133
|
+
this.logger.log(`Handling event of type "${event.type}" for notification with ID "${notification.id}"`, event);
|
|
134
|
+
try {
|
|
135
|
+
const data = yield __await(this.handleEvent(event, source));
|
|
136
|
+
if (!notification.transient) {
|
|
137
|
+
// keep track of the last handled notification for next time we fetch the notification stream
|
|
138
|
+
yield __await(this.setLastNotificationId(notification));
|
|
139
|
+
}
|
|
140
|
+
yield yield __await(Object.assign(Object.assign({}, data), { mappedEvent: data.mappedEvent ? this.cleanupPayloadBundle(data.mappedEvent) : undefined }));
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
this.logger.error(`There was an error with notification ID "${notification.id}": ${error.message}`, error);
|
|
144
|
+
const notificationError = {
|
|
145
|
+
error: error,
|
|
146
|
+
notification,
|
|
147
|
+
type: CoreError_1.CoreError.NOTIFICATION_ERROR,
|
|
148
|
+
};
|
|
149
|
+
this.emit(NotificationService.TOPIC.NOTIFICATION_ERROR, notificationError);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!notification.transient) {
|
|
153
|
+
// keep track of the last handled notification for next time we fetch the notification stream
|
|
154
|
+
yield __await(this.setLastNotificationId(notification));
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
cleanupPayloadBundle(payload) {
|
|
159
|
+
switch (payload.type) {
|
|
160
|
+
case conversation_1.PayloadBundleType.ASSET: {
|
|
161
|
+
const assetContent = payload.content;
|
|
162
|
+
const isMetaData = !!(assetContent === null || assetContent === void 0 ? void 0 : assetContent.original) && !(assetContent === null || assetContent === void 0 ? void 0 : assetContent.uploaded);
|
|
163
|
+
const isAbort = !!assetContent.abortReason || (!assetContent.original && !assetContent.uploaded);
|
|
164
|
+
if (isMetaData) {
|
|
165
|
+
payload.type = conversation_1.PayloadBundleType.ASSET_META;
|
|
166
|
+
}
|
|
167
|
+
else if (isAbort) {
|
|
168
|
+
payload.type = conversation_1.PayloadBundleType.ASSET_ABORT;
|
|
169
|
+
}
|
|
170
|
+
return payload;
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
return payload;
|
|
187
174
|
}
|
|
188
175
|
}
|
|
189
176
|
async handleEvent(event, source) {
|
|
190
177
|
switch (event.type) {
|
|
191
178
|
// Encrypted events
|
|
192
179
|
case Events.CONVERSATION_EVENT.OTR_MESSAGE_ADD: {
|
|
193
|
-
|
|
180
|
+
try {
|
|
181
|
+
const decryptedData = await this.cryptographyService.decryptMessage(event);
|
|
182
|
+
return {
|
|
183
|
+
mappedEvent: this.cryptographyService.mapGenericMessage(event, decryptedData, source),
|
|
184
|
+
event,
|
|
185
|
+
decryptedData,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
return { event, decryptionError: error };
|
|
190
|
+
}
|
|
194
191
|
}
|
|
195
192
|
// Meta events
|
|
196
193
|
case Events.CONVERSATION_EVENT.MEMBER_JOIN:
|
|
@@ -199,16 +196,17 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
199
196
|
case Events.CONVERSATION_EVENT.TYPING: {
|
|
200
197
|
const { conversation, from } = event;
|
|
201
198
|
const metaEvent = Object.assign(Object.assign({}, event), { conversation, from });
|
|
202
|
-
return ConversationMapper_1.ConversationMapper.mapConversationEvent(metaEvent, source);
|
|
199
|
+
return { mappedEvent: ConversationMapper_1.ConversationMapper.mapConversationEvent(metaEvent, source), event };
|
|
203
200
|
}
|
|
204
201
|
// User events
|
|
205
202
|
case Events.USER_EVENT.CONNECTION:
|
|
206
203
|
case Events.USER_EVENT.CLIENT_ADD:
|
|
207
204
|
case Events.USER_EVENT.UPDATE:
|
|
208
205
|
case Events.USER_EVENT.CLIENT_REMOVE: {
|
|
209
|
-
return UserMapper_1.UserMapper.mapUserEvent(event, this.apiClient.context.userId, source);
|
|
206
|
+
return { mappedEvent: UserMapper_1.UserMapper.mapUserEvent(event, this.apiClient.context.userId, source), event };
|
|
210
207
|
}
|
|
211
208
|
}
|
|
209
|
+
return { event };
|
|
212
210
|
}
|
|
213
211
|
}
|
|
214
212
|
exports.NotificationService = NotificationService;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UserEvent } from '@wireapp/api-client/src/event';
|
|
2
2
|
import { PayloadBundle, PayloadBundleSource } from '../conversation';
|
|
3
3
|
export declare class UserMapper {
|
|
4
|
-
static mapUserEvent(event: UserEvent, selfUserId: string, source: PayloadBundleSource): PayloadBundle |
|
|
4
|
+
static mapUserEvent(event: UserEvent, selfUserId: string, source: PayloadBundleSource): PayloadBundle | undefined;
|
|
5
5
|
}
|