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.
		
		
		
		
		
			
		
			
				
	
	
		
			210 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			210 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			TypeScript
		
	
| import AbortController, { AbortSignal } from 'abort-controller';
 | |
| import { isFinite, isUndefined, toNumber } from 'lodash';
 | |
| import {
 | |
|   OpenGroupData,
 | |
|   OpenGroupV2Room,
 | |
|   OpenGroupV2RoomWithImageID,
 | |
| } from '../../../../data/opengroups';
 | |
| import { MIME } from '../../../../types';
 | |
| import { processNewAttachment } from '../../../../types/MessageAttachment';
 | |
| import { roomHasBlindEnabled } from '../../../../types/sqlSharedTypes';
 | |
| import { callUtilsWorker } from '../../../../webworker/workers/browser/util_worker_interface';
 | |
| import { getConversationController } from '../../../conversations';
 | |
| import { OnionSending } from '../../../onions/onionSend';
 | |
| import { allowOnlyOneAtATime } from '../../../utils/Promise';
 | |
| import { OpenGroupPollingUtils } from '../opengroupV2/OpenGroupPollingUtils';
 | |
| import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils';
 | |
| 
 | |
| export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
 | |
|   serverUrl: string;
 | |
|   serverPubkey: string;
 | |
|   blinded: boolean;
 | |
|   abortSignal: AbortSignal;
 | |
|   headers: Record<string, any> | null;
 | |
|   roomId: string;
 | |
|   fileId: string;
 | |
|   throwError: boolean;
 | |
| }): Promise<Uint8Array | null> {
 | |
|   const {
 | |
|     serverUrl,
 | |
|     serverPubkey,
 | |
|     blinded,
 | |
|     abortSignal,
 | |
|     headers: includedHeaders,
 | |
|     roomId,
 | |
|     fileId,
 | |
|     throwError,
 | |
|   } = sendOptions;
 | |
| 
 | |
|   const stringifiedBody = null;
 | |
|   const method = 'GET';
 | |
|   const endpoint = `/room/${roomId}/file/${fileId}`;
 | |
|   if (!endpoint.startsWith('/')) {
 | |
|     throw new Error('endpoint needs a leading /');
 | |
|   }
 | |
|   const builtUrl = new URL(`${serverUrl}${endpoint}`);
 | |
|   let headersWithSogsHeadersIfNeeded = await OpenGroupPollingUtils.getOurOpenGroupHeaders(
 | |
|     serverPubkey,
 | |
|     endpoint,
 | |
|     method,
 | |
|     blinded,
 | |
|     stringifiedBody
 | |
|   );
 | |
| 
 | |
|   if (isUndefined(headersWithSogsHeadersIfNeeded)) {
 | |
|     return null;
 | |
|   }
 | |
|   headersWithSogsHeadersIfNeeded = { ...includedHeaders, ...headersWithSogsHeadersIfNeeded };
 | |
|   const res = await OnionSending.sendViaOnionV4ToNonSnodeWithRetries(
 | |
|     serverPubkey,
 | |
|     builtUrl,
 | |
|     {
 | |
|       method,
 | |
|       headers: headersWithSogsHeadersIfNeeded,
 | |
|       body: stringifiedBody,
 | |
|       useV4: true,
 | |
|     },
 | |
|     throwError,
 | |
|     abortSignal
 | |
|   );
 | |
| 
 | |
|   if (!res?.bodyBinary) {
 | |
|     window.log.info('fetchBinaryFromSogsWithOnionV4 no binary content with code', res?.status_code);
 | |
|     return null;
 | |
|   }
 | |
|   return res.bodyBinary;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This function fetches the avatar on an opengroup room based on the imageID, save it to the attachment folder and update the conversation avatar with the new path.
 | |
|  */
 | |
| export async function sogsV3FetchPreviewAndSaveIt(roomInfos: OpenGroupV2RoomWithImageID) {
 | |
|   const { roomId, serverUrl, imageID } = roomInfos;
 | |
| 
 | |
|   if (!imageID || Number.isNaN(Number(imageID))) {
 | |
|     window.log.warn(`imageId of room ${roomId} is not valid ${imageID}`);
 | |
|     return;
 | |
|   }
 | |
|   const imageIdNumber = toNumber(imageID);
 | |
| 
 | |
|   const convoId = getOpenGroupV2ConversationId(roomInfos.serverUrl, roomInfos.roomId);
 | |
|   let convo = getConversationController().get(convoId);
 | |
|   if (!convo) {
 | |
|     return;
 | |
|   }
 | |
|   let existingImageId = convo.get('avatarImageId');
 | |
|   if (existingImageId === imageIdNumber) {
 | |
|     // return early as the imageID about to be downloaded the one already set as avatar is the same.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const room = OpenGroupData.getV2OpenGroupRoom(convoId);
 | |
|   const blinded = roomHasBlindEnabled(room);
 | |
| 
 | |
|   // make sure this runs only once for each rooms.
 | |
|   // we don't want to trigger one of those on each setPollInfo results as it happens on each batch poll.
 | |
|   const oneAtAtimeResult = await allowOnlyOneAtATime(
 | |
|     `sogsV3FetchPreview-${serverUrl}-${roomId}`,
 | |
|     () => sogsV3FetchPreview(roomInfos, blinded)
 | |
|   );
 | |
| 
 | |
|   if (!oneAtAtimeResult || !oneAtAtimeResult?.byteLength) {
 | |
|     window?.log?.warn('sogsV3FetchPreviewAndSaveIt failed for room: ', roomId);
 | |
|     return;
 | |
|   }
 | |
|   // refresh to make sure the convo was not deleted during the fetch above
 | |
|   convo = getConversationController().get(convoId);
 | |
|   if (!convo) {
 | |
|     return;
 | |
|   }
 | |
|   existingImageId = convo.get('avatarImageId');
 | |
|   if (existingImageId !== imageIdNumber && isFinite(imageIdNumber)) {
 | |
|     // we have to trigger an update
 | |
|     // write the file to the disk (automatically encrypted),
 | |
| 
 | |
|     const upgradedAttachment = await processNewAttachment({
 | |
|       isRaw: true,
 | |
|       data: oneAtAtimeResult.buffer,
 | |
|       contentType: MIME.IMAGE_UNKNOWN, // contentType is mostly used to generate previews and screenshot. We do not care for those in this case.          // url: `${serverUrl}/${res.roomId}`,
 | |
|     });
 | |
| 
 | |
|     // update the hash on the conversationModel
 | |
|     // this does commit to DB and UI
 | |
|     await convo.setSessionProfile({
 | |
|       avatarPath: upgradedAttachment.path,
 | |
|       avatarImageId: imageIdNumber,
 | |
|     });
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This function can be used to fetch the default rooms (leftpane) images when the app starts.
 | |
|  * @returns the fetchedData in base64
 | |
|  */
 | |
| export async function sogsV3FetchPreviewBase64(roomInfos: OpenGroupV2RoomWithImageID) {
 | |
|   const fetched = await sogsV3FetchPreview(roomInfos, true); // left pane are session official default rooms, which do require blinded
 | |
|   if (fetched && fetched.byteLength) {
 | |
|     return callUtilsWorker('arrayBufferToStringBase64', fetched);
 | |
|   }
 | |
|   return null;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Download the preview image for that opengroup room.
 | |
|  * The returned value is a Uin8Array.
 | |
|  * It can be used directly, or saved on the attachments directory if needed (processNewAttachment), but this function does not handle it.
 | |
|  * Be sure to give the imageID field here, otherwise the request is dropped.
 | |
|  * This function does not check if the conversation exist, as it can be called for getting the preview image of the default rooms too. (left pane join default rooms)
 | |
|  * Those default rooms do not have a conversation associated with them, as they are not joined yet
 | |
|  */
 | |
| const sogsV3FetchPreview = async (
 | |
|   roomInfos: OpenGroupV2RoomWithImageID,
 | |
|   blinded: boolean
 | |
| ): Promise<Uint8Array | null> => {
 | |
|   if (!roomInfos || !roomInfos.imageID) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   // not a batch call yet as we need to exclude headers for this call for now
 | |
|   const fetched = await fetchBinaryFromSogsWithOnionV4({
 | |
|     abortSignal: new AbortController().signal,
 | |
|     blinded,
 | |
|     headers: null,
 | |
|     serverPubkey: roomInfos.serverPublicKey,
 | |
|     serverUrl: roomInfos.serverUrl,
 | |
|     roomId: roomInfos.roomId,
 | |
|     fileId: roomInfos.imageID,
 | |
|     throwError: false,
 | |
|   });
 | |
|   if (fetched && fetched.byteLength) {
 | |
|     return fetched;
 | |
|   }
 | |
|   return null;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Download the file fileID in that opengroup room.
 | |
|  * The returned value is a base64 string.
 | |
|  * It can be used directly, or saved on the attachments directory if needed, but this function does not handle it.
 | |
|  */
 | |
| export const sogsV3FetchFileByFileID = async (
 | |
|   roomInfos: OpenGroupV2Room,
 | |
|   fileId: string
 | |
| ): Promise<Uint8Array | null> => {
 | |
|   if (!roomInfos) {
 | |
|     return null;
 | |
|   }
 | |
|   // not a batch call yet as we need to exclude headers for this call for now
 | |
|   const fetched = await fetchBinaryFromSogsWithOnionV4({
 | |
|     abortSignal: new AbortController().signal,
 | |
|     blinded: roomHasBlindEnabled(roomInfos),
 | |
|     headers: null,
 | |
|     serverPubkey: roomInfos.serverPublicKey,
 | |
|     serverUrl: roomInfos.serverUrl,
 | |
|     roomId: roomInfos.roomId,
 | |
|     fileId,
 | |
|     throwError: true,
 | |
|   });
 | |
|   return fetched && fetched.byteLength ? fetched : null;
 | |
| };
 |