|
@@ -265,6 +265,8 @@ export class WebClientService {
|
|
|
private drafts: threema.Container.Drafts;
|
|
|
private pcHelper: PeerConnectionHelper = null;
|
|
|
private trustedKeyStore: TrustedKeyStoreService;
|
|
|
+ private keyStore: saltyrtc.KeyStore | null = null;
|
|
|
+ private peerTrustedKey: Uint8Array | null = null;
|
|
|
public clientInfo: threema.ClientInfo = null;
|
|
|
public version = null;
|
|
|
|
|
@@ -445,18 +447,27 @@ export class WebClientService {
|
|
|
/**
|
|
|
* Initialize the webclient service.
|
|
|
*
|
|
|
+ * @param keyStore Key store to be used. Will create a new one if `undefined` or reuse the currently used one if
|
|
|
+ * `reuse`.
|
|
|
+ * @param peerTrustedKey The peer's trusted key. Will not use this feature if `undefined` or reuse the currently
|
|
|
+ * used one if `reuse`.
|
|
|
+ * @param resume Whether the previous session can be resumed.
|
|
|
+ *
|
|
|
* Warning: Do not call this with `flags.resume` set to `false` in case
|
|
|
* messages can be queued by the user.
|
|
|
*/
|
|
|
public init(flags: {
|
|
|
- keyStore?: saltyrtc.KeyStore,
|
|
|
- peerTrustedKey?: Uint8Array,
|
|
|
+ keyStore?: saltyrtc.KeyStore | 'reuse',
|
|
|
+ peerTrustedKey?: Uint8Array | 'reuse',
|
|
|
resume: boolean,
|
|
|
}): void {
|
|
|
let keyStore = flags.keyStore;
|
|
|
let resumeSession = flags.resume;
|
|
|
- this.log.info(`Initializing (keyStore=${keyStore !== undefined ? 'yes' : 'no'}, peerTrustedKey=` +
|
|
|
- `${flags.peerTrustedKey !== undefined ? 'yes' : 'no'}, resume=${resumeSession})`);
|
|
|
+ let peerTrustedKey = flags.peerTrustedKey;
|
|
|
+ const keyStoreInfo = keyStore === 'reuse' ? 'reuse' : (keyStore !== undefined ? 'yes' : 'no');
|
|
|
+ const peerTrustedKeyInfo = peerTrustedKey === 'reuse' ? 'reuse' : (peerTrustedKey !== undefined ? 'yes' : 'no');
|
|
|
+ this.log.info(`Initializing (keyStore=${keyStoreInfo}, peerTrustedKey=${peerTrustedKeyInfo}, ` + `
|
|
|
+ resume=${resumeSession})`);
|
|
|
|
|
|
// Reset fields, blob cache, pending requests and pending timeouts in case the session
|
|
|
// should explicitly not be resumed
|
|
@@ -524,9 +535,16 @@ export class WebClientService {
|
|
|
tasks.push(this.relayedDataTask);
|
|
|
|
|
|
// Create new keystore if necessary
|
|
|
- if (!keyStore) {
|
|
|
+ if (keyStore === 'reuse') {
|
|
|
+ if (this.keyStore === null) {
|
|
|
+ throw new Error('Key store cannot be reused as it is null');
|
|
|
+ }
|
|
|
+ keyStore = this.keyStore;
|
|
|
+ }
|
|
|
+ if (keyStore === undefined) {
|
|
|
keyStore = new saltyrtcClient.KeyStore();
|
|
|
}
|
|
|
+ this.keyStore = keyStore;
|
|
|
|
|
|
// Determine SaltyRTC host, replace the inner prefix (if any)
|
|
|
this.saltyRtcHost = this.config.SALTYRTC_HOST.replace('{prefix}', keyStore.publicKeyHex.substr(0, 2));
|
|
@@ -539,8 +557,15 @@ export class WebClientService {
|
|
|
.withKeyStore(keyStore)
|
|
|
.usingTasks(tasks)
|
|
|
.withPingInterval(30);
|
|
|
- if (flags.peerTrustedKey !== undefined) {
|
|
|
- builder = builder.withTrustedPeerKey(flags.peerTrustedKey);
|
|
|
+ if (peerTrustedKey === 'reuse') {
|
|
|
+ if (this.peerTrustedKey === null) {
|
|
|
+ throw new Error('Peer trusted key cannot be reused as it is null');
|
|
|
+ }
|
|
|
+ peerTrustedKey = this.peerTrustedKey;
|
|
|
+ }
|
|
|
+ if (peerTrustedKey !== undefined) {
|
|
|
+ builder = builder.withTrustedPeerKey(peerTrustedKey);
|
|
|
+ this.peerTrustedKey = peerTrustedKey;
|
|
|
}
|
|
|
this.salty = builder.asInitiator();
|
|
|
this.arpLog.info('Public key:', this.salty.permanentKeyHex);
|
|
@@ -577,6 +602,17 @@ export class WebClientService {
|
|
|
this.arpLog.warn('Unknown signaling state:', state);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // Close the peer connection.
|
|
|
+ // Note: For some reason, we do not receive 'close' events for the main data channel,
|
|
|
+ // so this is a fallback mechanism.
|
|
|
+ if (this.pcHelper !== null && state === 'closed') {
|
|
|
+ this.arpLog.debug('Closing peer connection');
|
|
|
+ this.stateService.updateTaskConnectionState(threema.TaskConnectionState.Disconnected);
|
|
|
+ this.pcHelper.onConnectionStateChange = null;
|
|
|
+ this.pcHelper.close();
|
|
|
+ }
|
|
|
+
|
|
|
this.stateService.updateSignalingConnectionState(state, this.chosenTask, this.handoverDone);
|
|
|
});
|
|
|
|
|
@@ -862,6 +898,9 @@ export class WebClientService {
|
|
|
* considered established.
|
|
|
*/
|
|
|
private onTaskEstablished(resumeSession: boolean) {
|
|
|
+ // Store peer's key
|
|
|
+ this.peerTrustedKey = this.salty.peerPermanentKeyBytes;
|
|
|
+
|
|
|
// Pushing complete
|
|
|
this.resetPushSession(true);
|
|
|
|
|
@@ -1086,7 +1125,7 @@ export class WebClientService {
|
|
|
// Determine chunk length
|
|
|
this.secureDataChannelChunkLength = Math.min(
|
|
|
WebClientService.DATA_CHANNEL_MAX_CHUNK_SIZE, this.pcHelper.pc.sctp.maxMessageSize);
|
|
|
- this.arpLog.debug(`Using chunk length: ${this.secureDataChannelChunkLength} for data channel` +
|
|
|
+ this.arpLog.debug(`Using chunk length: ${this.secureDataChannelChunkLength} for data channel ` +
|
|
|
dc.label);
|
|
|
|
|
|
// Connection established
|
|
@@ -1095,8 +1134,10 @@ export class WebClientService {
|
|
|
});
|
|
|
};
|
|
|
dc.onclose = () => {
|
|
|
- this.arpLog.error(`Data channel ${dc.label} closed prematurely`);
|
|
|
- this.failSession();
|
|
|
+ this.arpLog.info(`Data channel ${dc.label} closed, closing peer connection`);
|
|
|
+ this.stateService.updateTaskConnectionState(threema.TaskConnectionState.Disconnected);
|
|
|
+ this.pcHelper.onConnectionStateChange = null;
|
|
|
+ this.pcHelper.close();
|
|
|
};
|
|
|
dc.onerror = (event) => {
|
|
|
this.arpLog.error(`Data channel ${dc.label} error:`, event);
|
|
@@ -1344,6 +1385,8 @@ export class WebClientService {
|
|
|
|
|
|
// Clear stored data (trusted key, push token, etc) if deleting the session
|
|
|
if (remove) {
|
|
|
+ this.keyStore = null;
|
|
|
+ this.peerTrustedKey = null;
|
|
|
this.trustedKeyStore.clearTrustedKey();
|
|
|
}
|
|
|
|