Send support for Sender Key
This commit is contained in:
		
					parent
					
						
							
								d8417e562b
							
						
					
				
			
			
				commit
				
					
						e6f1ec2b6b
					
				
			
		
					 30 changed files with 2290 additions and 911 deletions
				
			
		| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
 | 
			
		||||
import PQueue from 'p-queue';
 | 
			
		||||
import { isNumber } from 'lodash';
 | 
			
		||||
import * as z from 'zod';
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  Direction,
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +32,7 @@ import {
 | 
			
		|||
  sessionStructureToArrayBuffer,
 | 
			
		||||
} from './util/sessionTranslation';
 | 
			
		||||
import {
 | 
			
		||||
  DeviceType,
 | 
			
		||||
  KeyPairType,
 | 
			
		||||
  IdentityKeyType,
 | 
			
		||||
  SenderKeyType,
 | 
			
		||||
| 
						 | 
				
			
			@ -545,7 +546,7 @@ export class SignalProtocolStore extends EventsMixin {
 | 
			
		|||
      }
 | 
			
		||||
 | 
			
		||||
      if (entry.hydrated) {
 | 
			
		||||
        window.log.info('Successfully fetched signed prekey (cache hit):', id);
 | 
			
		||||
        window.log.info('Successfully fetched sender key (cache hit):', id);
 | 
			
		||||
        return entry.item;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -555,17 +556,40 @@ export class SignalProtocolStore extends EventsMixin {
 | 
			
		|||
        item,
 | 
			
		||||
        fromDB: entry.fromDB,
 | 
			
		||||
      });
 | 
			
		||||
      window.log.info('Successfully fetched signed prekey (cache miss):', id);
 | 
			
		||||
      window.log.info('Successfully fetched sender key(cache miss):', id);
 | 
			
		||||
      return item;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      const errorString = error && error.stack ? error.stack : error;
 | 
			
		||||
      window.log.error(
 | 
			
		||||
        `getSenderKey: failed to load senderKey ${encodedAddress}/${distributionId}: ${errorString}`
 | 
			
		||||
        `getSenderKey: failed to load sender key ${encodedAddress}/${distributionId}: ${errorString}`
 | 
			
		||||
      );
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async removeSenderKey(
 | 
			
		||||
    encodedAddress: string,
 | 
			
		||||
    distributionId: string
 | 
			
		||||
  ): Promise<void> {
 | 
			
		||||
    if (!this.senderKeys) {
 | 
			
		||||
      throw new Error('getSenderKey: this.senderKeys not yet cached!');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const senderId = await normalizeEncodedAddress(encodedAddress);
 | 
			
		||||
      const id = this.getSenderKeyId(senderId, distributionId);
 | 
			
		||||
 | 
			
		||||
      await window.Signal.Data.removeSenderKeyById(id);
 | 
			
		||||
 | 
			
		||||
      this.senderKeys.delete(id);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      const errorString = error && error.stack ? error.stack : error;
 | 
			
		||||
      window.log.error(
 | 
			
		||||
        `removeSenderKey: failed to remove senderKey ${encodedAddress}/${distributionId}: ${errorString}`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Session Queue
 | 
			
		||||
 | 
			
		||||
  async enqueueSessionJob<T>(
 | 
			
		||||
| 
						 | 
				
			
			@ -792,6 +816,21 @@ export class SignalProtocolStore extends EventsMixin {
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async loadSessions(
 | 
			
		||||
    encodedAddresses: Array<string>,
 | 
			
		||||
    { zone = GLOBAL_ZONE }: SessionTransactionOptions = {}
 | 
			
		||||
  ): Promise<Array<SessionRecord>> {
 | 
			
		||||
    return this.withZone(zone, 'loadSession', async () => {
 | 
			
		||||
      const sessions = await Promise.all(
 | 
			
		||||
        encodedAddresses.map(async address =>
 | 
			
		||||
          this.loadSession(address, { zone })
 | 
			
		||||
        )
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      return sessions.filter(isNotNil);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _maybeMigrateSession(
 | 
			
		||||
    session: SessionType
 | 
			
		||||
  ): Promise<SessionRecord> {
 | 
			
		||||
| 
						 | 
				
			
			@ -882,33 +921,51 @@ export class SignalProtocolStore extends EventsMixin {
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getDeviceIds(identifier: string): Promise<Array<number>> {
 | 
			
		||||
    return this.withZone(GLOBAL_ZONE, 'getDeviceIds', async () => {
 | 
			
		||||
  async getOpenDevices(
 | 
			
		||||
    identifiers: Array<string>
 | 
			
		||||
  ): Promise<{
 | 
			
		||||
    devices: Array<DeviceType>;
 | 
			
		||||
    emptyIdentifiers: Array<string>;
 | 
			
		||||
  }> {
 | 
			
		||||
    return this.withZone(GLOBAL_ZONE, 'getOpenDevices', async () => {
 | 
			
		||||
      if (!this.sessions) {
 | 
			
		||||
        throw new Error('getDeviceIds: this.sessions not yet cached!');
 | 
			
		||||
        throw new Error('getOpenDevices: this.sessions not yet cached!');
 | 
			
		||||
      }
 | 
			
		||||
      if (identifier === null || identifier === undefined) {
 | 
			
		||||
        throw new Error('getDeviceIds: identifier was undefined/null');
 | 
			
		||||
      if (identifiers.length === 0) {
 | 
			
		||||
        throw new Error('getOpenDevices: No identifiers provided!');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        const id = window.ConversationController.getConversationId(identifier);
 | 
			
		||||
        if (!id) {
 | 
			
		||||
          throw new Error(
 | 
			
		||||
            `getDeviceIds: No conversationId found for identifier ${identifier}`
 | 
			
		||||
        const conversationIds = new Map<string, string>();
 | 
			
		||||
        identifiers.forEach(identifier => {
 | 
			
		||||
          if (identifier === null || identifier === undefined) {
 | 
			
		||||
            throw new Error('getOpenDevices: identifier was undefined/null');
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const conversation = window.ConversationController.getOrCreate(
 | 
			
		||||
            identifier,
 | 
			
		||||
            'private'
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
          if (!conversation) {
 | 
			
		||||
            throw new Error(
 | 
			
		||||
              `getOpenDevices: No conversationId found for identifier ${identifier}`
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          conversationIds.set(conversation.get('id'), identifier);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const allSessions = this._getAllSessions();
 | 
			
		||||
        const entries = allSessions.filter(
 | 
			
		||||
          session => session.fromDB.conversationId === id
 | 
			
		||||
        const entries = allSessions.filter(session =>
 | 
			
		||||
          conversationIds.has(session.fromDB.conversationId)
 | 
			
		||||
        );
 | 
			
		||||
        const openIds = await Promise.all(
 | 
			
		||||
        const openEntries: Array<
 | 
			
		||||
          SessionCacheEntry | undefined
 | 
			
		||||
        > = await Promise.all(
 | 
			
		||||
          entries.map(async entry => {
 | 
			
		||||
            if (entry.hydrated) {
 | 
			
		||||
              const record = entry.item;
 | 
			
		||||
              if (record.hasCurrentState()) {
 | 
			
		||||
                return entry.fromDB.deviceId;
 | 
			
		||||
                return entry;
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              return undefined;
 | 
			
		||||
| 
						 | 
				
			
			@ -916,25 +973,67 @@ export class SignalProtocolStore extends EventsMixin {
 | 
			
		|||
 | 
			
		||||
            const record = await this._maybeMigrateSession(entry.fromDB);
 | 
			
		||||
            if (record.hasCurrentState()) {
 | 
			
		||||
              return entry.fromDB.deviceId;
 | 
			
		||||
              return entry;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return undefined;
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return openIds.filter(isNotNil);
 | 
			
		||||
        const devices = openEntries
 | 
			
		||||
          .map(entry => {
 | 
			
		||||
            if (!entry) {
 | 
			
		||||
              return undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const { conversationId } = entry.fromDB;
 | 
			
		||||
            conversationIds.delete(conversationId);
 | 
			
		||||
 | 
			
		||||
            const id = entry.fromDB.deviceId;
 | 
			
		||||
            const conversation = window.ConversationController.get(
 | 
			
		||||
              conversationId
 | 
			
		||||
            );
 | 
			
		||||
            if (!conversation) {
 | 
			
		||||
              throw new Error(
 | 
			
		||||
                `getOpenDevices: Unable to find matching conversation for ${conversationId}`
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const identifier =
 | 
			
		||||
              conversation.get('uuid') || conversation.get('e164');
 | 
			
		||||
            if (!identifier) {
 | 
			
		||||
              throw new Error(
 | 
			
		||||
                `getOpenDevices: No identifier for conversation ${conversationId}`
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
              identifier,
 | 
			
		||||
              id,
 | 
			
		||||
            };
 | 
			
		||||
          })
 | 
			
		||||
          .filter(isNotNil);
 | 
			
		||||
        const emptyIdentifiers = Array.from(conversationIds.values());
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          devices,
 | 
			
		||||
          emptyIdentifiers,
 | 
			
		||||
        };
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        window.log.error(
 | 
			
		||||
          `getDeviceIds: Failed to get device ids for identifier ${identifier}`,
 | 
			
		||||
          'getOpenDevices: Failed to get devices',
 | 
			
		||||
          error && error.stack ? error.stack : error
 | 
			
		||||
        );
 | 
			
		||||
        throw error;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return [];
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getDeviceIds(identifier: string): Promise<Array<number>> {
 | 
			
		||||
    const { devices } = await this.getOpenDevices([identifier]);
 | 
			
		||||
    return devices.map((device: DeviceType) => device.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async removeSession(encodedAddress: string): Promise<void> {
 | 
			
		||||
    return this.withZone(GLOBAL_ZONE, 'removeSession', async () => {
 | 
			
		||||
      if (!this.sessions) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue