You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.7 KiB
TypeScript
152 lines
4.7 KiB
TypeScript
// REMOVE COMMENT AFTER: This can just export pure functions as it doesn't need state
|
|
|
|
import { RawMessage } from '../types/RawMessage';
|
|
import { OpenGroupMessage } from '../messages/outgoing';
|
|
import { SignalService } from '../../protobuf';
|
|
import { MessageEncrypter } from '../crypto';
|
|
import pRetry from 'p-retry';
|
|
import { PubKey } from '../types';
|
|
import { UserUtils } from '../utils';
|
|
import { OpenGroupRequestCommonType } from '../../opengroup/opengroupV2/ApiUtil';
|
|
import { postMessage } from '../../opengroup/opengroupV2/OpenGroupAPIV2';
|
|
import { OpenGroupMessageV2 } from '../../opengroup/opengroupV2/OpenGroupMessageV2';
|
|
import { fromUInt8ArrayToBase64 } from '../utils/String';
|
|
import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
|
|
import * as LokiMessageApi from './LokiMessageApi';
|
|
import { addMessagePadding } from '../crypto/MessagePadding';
|
|
|
|
// ================ Regular ================
|
|
|
|
/**
|
|
* Send a message via service nodes.
|
|
*
|
|
* @param message The message to send.
|
|
* @param attempts The amount of times to attempt sending. Minimum value is 1.
|
|
*/
|
|
export async function send(
|
|
message: RawMessage,
|
|
attempts: number = 3,
|
|
retryMinTimeout?: number // in ms
|
|
): Promise<Uint8Array> {
|
|
const device = PubKey.cast(message.device);
|
|
const { plainTextBuffer, encryption, timestamp, ttl } = message;
|
|
const { envelopeType, cipherText } = await MessageEncrypter.encrypt(
|
|
device,
|
|
plainTextBuffer,
|
|
encryption
|
|
);
|
|
const envelope = await buildEnvelope(envelopeType, device.key, timestamp, cipherText);
|
|
window?.log?.debug('Sending envelope', envelope, ' to ', device.key);
|
|
const data = wrapEnvelope(envelope);
|
|
|
|
return pRetry(
|
|
async () => {
|
|
await LokiMessageApi.sendMessage(device.key, data, timestamp, ttl);
|
|
return data;
|
|
},
|
|
{
|
|
retries: Math.max(attempts - 1, 0),
|
|
factor: 1,
|
|
minTimeout: retryMinTimeout || 1000,
|
|
}
|
|
);
|
|
}
|
|
|
|
async function buildEnvelope(
|
|
type: SignalService.Envelope.Type,
|
|
sskSource: string | undefined,
|
|
timestamp: number,
|
|
content: Uint8Array
|
|
): Promise<SignalService.Envelope> {
|
|
let source: string | undefined;
|
|
|
|
if (type === SignalService.Envelope.Type.CLOSED_GROUP_CIPHERTEXT) {
|
|
source = sskSource;
|
|
}
|
|
|
|
return SignalService.Envelope.create({
|
|
type,
|
|
source,
|
|
timestamp,
|
|
content,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This is an outdated practice and we should probably just send the envelope data directly.
|
|
* Something to think about in the future.
|
|
*/
|
|
function wrapEnvelope(envelope: SignalService.Envelope): Uint8Array {
|
|
const request = SignalService.WebSocketRequestMessage.create({
|
|
id: 0,
|
|
body: SignalService.Envelope.encode(envelope).finish(),
|
|
verb: 'PUT',
|
|
path: '/api/v1/message',
|
|
});
|
|
|
|
const websocket = SignalService.WebSocketMessage.create({
|
|
type: SignalService.WebSocketMessage.Type.REQUEST,
|
|
request,
|
|
});
|
|
return SignalService.WebSocketMessage.encode(websocket).finish();
|
|
}
|
|
|
|
// ================ Open Group ================
|
|
|
|
/**
|
|
* Deprecated Send a message to an open group v2.
|
|
* @param message The open group message.
|
|
*/
|
|
export async function sendToOpenGroup(
|
|
message: OpenGroupMessage
|
|
): Promise<{ serverId: number; serverTimestamp: number }> {
|
|
/*
|
|
Note: Retrying wasn't added to this but it can be added in the future if needed.
|
|
The only problem is that `channelAPI.sendMessage` returns true/false and doesn't throw any error so we can never be sure why sending failed.
|
|
This should be fixed and we shouldn't rely on returning true/false, rather return nothing (success) or throw an error (failure)
|
|
*/
|
|
const { group, quote, attachments, preview, body, timestamp } = message;
|
|
const channelAPI = await window.lokiPublicChatAPI.findOrCreateChannel(
|
|
group.server,
|
|
group.channel,
|
|
group.conversationId
|
|
);
|
|
|
|
if (!channelAPI) {
|
|
return { serverId: -1, serverTimestamp: -1 };
|
|
}
|
|
|
|
// Returns -1 on fail or an id > 0 on success
|
|
return channelAPI.sendMessage(
|
|
{
|
|
quote,
|
|
attachments: attachments || [],
|
|
preview: preview || [],
|
|
body,
|
|
},
|
|
timestamp
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Deprecated Send a message to an open group v2.
|
|
* @param message The open group message.
|
|
*/
|
|
export async function sendToOpenGroupV2(
|
|
rawMessage: OpenGroupVisibleMessage,
|
|
roomInfos: OpenGroupRequestCommonType
|
|
): Promise<OpenGroupMessageV2> {
|
|
// we agreed to pad message for opengroupv2
|
|
const paddedBody = addMessagePadding(rawMessage.plainTextBuffer());
|
|
const v2Message = new OpenGroupMessageV2({
|
|
sentTimestamp: Date.now(),
|
|
sender: UserUtils.getOurPubKeyStrFromCache(),
|
|
base64EncodedData: fromUInt8ArrayToBase64(paddedBody),
|
|
// the signature is added in the postMessage())
|
|
});
|
|
|
|
// Warning: postMessage throws
|
|
const sentMessage = await postMessage(v2Message, roomInfos);
|
|
return sentMessage;
|
|
}
|