@wireapp/core 27.0.5 → 27.2.1
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 +30 -0
- package/package.json +3 -3
- package/src/main/Account.d.ts +42 -5
- package/src/main/Account.js +64 -28
- package/src/main/cryptography/CryptographyService.d.ts +8 -1
- package/src/main/cryptography/CryptographyService.js +35 -3
- package/src/main/notification/NotificationBackendRepository.d.ts +4 -1
- package/src/main/notification/NotificationService.d.ts +19 -32
- package/src/main/notification/NotificationService.js +82 -81
- package/src/main/user/UserMapper.d.ts +1 -1
- package/src/main/user/UserMapper.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
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.2.1](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.2.0...@wireapp/core@27.2.1) (2022-05-31)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @wireapp/core
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [27.2.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.1.0...@wireapp/core@27.2.0) (2022-05-30)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* Add missed notifications hook ([#4275](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4275)) ([f81217b](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/f81217bad53fed2e4fb163c6ae871b39ca16cf58))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# [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)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* 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))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
## [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
37
|
|
|
8
38
|
**Note:** Version bump only for package @wireapp/core
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@types/long": "4.0.1",
|
|
7
7
|
"@types/node": "~14",
|
|
8
|
-
"@wireapp/api-client": "19.
|
|
8
|
+
"@wireapp/api-client": "19.3.1",
|
|
9
9
|
"@wireapp/cryptobox": "12.8.0",
|
|
10
10
|
"bazinga64": "5.10.0",
|
|
11
11
|
"hash.js": "1.1.7",
|
|
@@ -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.
|
|
73
|
-
"gitHead": "
|
|
72
|
+
"version": "27.2.1",
|
|
73
|
+
"gitHead": "97d45ca6fbd2c9f72871882b3cd0b92b26d49cf3"
|
|
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,44 @@ 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, onMissedNotifications, }?: {
|
|
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
|
+
/**
|
|
176
|
+
* called when we detect lost notification from backend.
|
|
177
|
+
* When a client doesn't log in for a while (28 days, as of now) notifications that are older than 28 days will be deleted from backend.
|
|
178
|
+
* If the client query the backend for the notifications since a particular notification ID and this ID doesn't exist anymore on the backend, we deduce that some messages were not sync before they were removed from backend.
|
|
179
|
+
* We can then detect that something was wrong and warn the consumer that there might be some missing old messages
|
|
180
|
+
* @param {string} notificationId
|
|
181
|
+
*/
|
|
182
|
+
onMissedNotifications?: (notificationId: string) => void;
|
|
183
|
+
}): Promise<() => void>;
|
|
147
184
|
private initEngine;
|
|
148
185
|
}
|
|
149
186
|
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 = () => { }, onMissedNotifications = () => { }, } = {}) {
|
|
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);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
this.
|
|
298
|
-
this.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
this.service.notification.
|
|
302
|
-
|
|
303
|
-
|
|
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);
|
|
335
|
+
}, onMissedNotifications);
|
|
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
|
|
@@ -3,6 +3,9 @@ import type { Notification } from '@wireapp/api-client/src/notification/';
|
|
|
3
3
|
export declare class NotificationBackendRepository {
|
|
4
4
|
private readonly apiClient;
|
|
5
5
|
constructor(apiClient: APIClient);
|
|
6
|
-
getAllNotifications(clientId?: string, lastNotificationId?: string): Promise<
|
|
6
|
+
getAllNotifications(clientId?: string, lastNotificationId?: string): Promise<{
|
|
7
|
+
notifications: Notification[];
|
|
8
|
+
missedNotification?: string | undefined;
|
|
9
|
+
}>;
|
|
7
10
|
getLastNotification(clientId?: string): Promise<Notification>;
|
|
8
11
|
}
|
|
@@ -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,15 +35,16 @@ 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>;
|
|
56
42
|
getNotificationEventList(): Promise<Events.BackendEvent[]>;
|
|
57
43
|
setLastEventDate(eventDate: Date): Promise<Date>;
|
|
58
44
|
setLastNotificationId(lastNotification: Notification): Promise<string>;
|
|
59
|
-
handleNotificationStream(notificationHandler: NotificationHandler): Promise<void>;
|
|
60
|
-
|
|
45
|
+
handleNotificationStream(notificationHandler: NotificationHandler, onMissedNotifications: (notificationId: string) => void): Promise<void>;
|
|
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);
|
|
@@ -180,17 +118,79 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
180
118
|
async setLastNotificationId(lastNotification) {
|
|
181
119
|
return this.database.updateLastNotificationId(lastNotification);
|
|
182
120
|
}
|
|
183
|
-
async handleNotificationStream(notificationHandler) {
|
|
184
|
-
const notifications = await this.getAllNotifications();
|
|
185
|
-
|
|
186
|
-
|
|
121
|
+
async handleNotificationStream(notificationHandler, onMissedNotifications) {
|
|
122
|
+
const { notifications, missedNotification } = await this.getAllNotifications();
|
|
123
|
+
if (missedNotification) {
|
|
124
|
+
onMissedNotifications(missedNotification);
|
|
125
|
+
}
|
|
126
|
+
for (const [index, notification] of notifications.entries()) {
|
|
127
|
+
await notificationHandler(notification, conversation_1.PayloadBundleSource.NOTIFICATION_STREAM, {
|
|
128
|
+
done: index + 1,
|
|
129
|
+
total: notifications.length,
|
|
130
|
+
}).catch(error => this.logger.error(error));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
handleNotification(notification, source) {
|
|
134
|
+
return __asyncGenerator(this, arguments, function* handleNotification_1() {
|
|
135
|
+
for (const event of notification.payload) {
|
|
136
|
+
this.logger.log(`Handling event of type "${event.type}" for notification with ID "${notification.id}"`, event);
|
|
137
|
+
try {
|
|
138
|
+
const data = yield __await(this.handleEvent(event, source));
|
|
139
|
+
if (!notification.transient) {
|
|
140
|
+
// keep track of the last handled notification for next time we fetch the notification stream
|
|
141
|
+
yield __await(this.setLastNotificationId(notification));
|
|
142
|
+
}
|
|
143
|
+
yield yield __await(Object.assign(Object.assign({}, data), { mappedEvent: data.mappedEvent ? this.cleanupPayloadBundle(data.mappedEvent) : undefined }));
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.logger.error(`There was an error with notification ID "${notification.id}": ${error.message}`, error);
|
|
147
|
+
const notificationError = {
|
|
148
|
+
error: error,
|
|
149
|
+
notification,
|
|
150
|
+
type: CoreError_1.CoreError.NOTIFICATION_ERROR,
|
|
151
|
+
};
|
|
152
|
+
this.emit(NotificationService.TOPIC.NOTIFICATION_ERROR, notificationError);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!notification.transient) {
|
|
156
|
+
// keep track of the last handled notification for next time we fetch the notification stream
|
|
157
|
+
yield __await(this.setLastNotificationId(notification));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
cleanupPayloadBundle(payload) {
|
|
162
|
+
switch (payload.type) {
|
|
163
|
+
case conversation_1.PayloadBundleType.ASSET: {
|
|
164
|
+
const assetContent = payload.content;
|
|
165
|
+
const isMetaData = !!(assetContent === null || assetContent === void 0 ? void 0 : assetContent.original) && !(assetContent === null || assetContent === void 0 ? void 0 : assetContent.uploaded);
|
|
166
|
+
const isAbort = !!assetContent.abortReason || (!assetContent.original && !assetContent.uploaded);
|
|
167
|
+
if (isMetaData) {
|
|
168
|
+
payload.type = conversation_1.PayloadBundleType.ASSET_META;
|
|
169
|
+
}
|
|
170
|
+
else if (isAbort) {
|
|
171
|
+
payload.type = conversation_1.PayloadBundleType.ASSET_ABORT;
|
|
172
|
+
}
|
|
173
|
+
return payload;
|
|
174
|
+
}
|
|
175
|
+
default:
|
|
176
|
+
return payload;
|
|
187
177
|
}
|
|
188
178
|
}
|
|
189
179
|
async handleEvent(event, source) {
|
|
190
180
|
switch (event.type) {
|
|
191
181
|
// Encrypted events
|
|
192
182
|
case Events.CONVERSATION_EVENT.OTR_MESSAGE_ADD: {
|
|
193
|
-
|
|
183
|
+
try {
|
|
184
|
+
const decryptedData = await this.cryptographyService.decryptMessage(event);
|
|
185
|
+
return {
|
|
186
|
+
mappedEvent: this.cryptographyService.mapGenericMessage(event, decryptedData, source),
|
|
187
|
+
event,
|
|
188
|
+
decryptedData,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
return { event, decryptionError: error };
|
|
193
|
+
}
|
|
194
194
|
}
|
|
195
195
|
// Meta events
|
|
196
196
|
case Events.CONVERSATION_EVENT.MEMBER_JOIN:
|
|
@@ -199,16 +199,17 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
199
199
|
case Events.CONVERSATION_EVENT.TYPING: {
|
|
200
200
|
const { conversation, from } = event;
|
|
201
201
|
const metaEvent = Object.assign(Object.assign({}, event), { conversation, from });
|
|
202
|
-
return ConversationMapper_1.ConversationMapper.mapConversationEvent(metaEvent, source);
|
|
202
|
+
return { mappedEvent: ConversationMapper_1.ConversationMapper.mapConversationEvent(metaEvent, source), event };
|
|
203
203
|
}
|
|
204
204
|
// User events
|
|
205
205
|
case Events.USER_EVENT.CONNECTION:
|
|
206
206
|
case Events.USER_EVENT.CLIENT_ADD:
|
|
207
207
|
case Events.USER_EVENT.UPDATE:
|
|
208
208
|
case Events.USER_EVENT.CLIENT_REMOVE: {
|
|
209
|
-
return UserMapper_1.UserMapper.mapUserEvent(event, this.apiClient.context.userId, source);
|
|
209
|
+
return { mappedEvent: UserMapper_1.UserMapper.mapUserEvent(event, this.apiClient.context.userId, source), event };
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
+
return { event };
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
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
|
}
|