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.
163 lines
5.0 KiB
TypeScript
163 lines
5.0 KiB
TypeScript
import AbortController from 'abort-controller';
|
|
import { BlindingActions } from '../../../webworker/workers/browser/libsession_worker_interface';
|
|
import { OnionSending, OnionV4JSONSnodeResponse } from '../../onions/onionSend';
|
|
import {
|
|
batchGlobalIsSuccess,
|
|
parseBatchGlobalStatusCode,
|
|
} from '../open_group_api/sogsv3/sogsV3BatchPoll';
|
|
import { GetNetworkTime } from '../snode_api/getNetworkTime';
|
|
import { fromUInt8ArrayToBase64 } from '../../utils/String';
|
|
|
|
export const fileServerHost = 'filev2.getsession.org';
|
|
export const fileServerURL = `http://${fileServerHost}`;
|
|
|
|
export const fileServerPubKey = 'da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59';
|
|
const RELEASE_VERSION_ENDPOINT = '/session_version?platform=desktop';
|
|
|
|
const POST_GET_FILE_ENDPOINT = '/file';
|
|
|
|
/**
|
|
* Upload a file to the file server v2 using the onion v4 encoding
|
|
* @param fileContent the data to send
|
|
* @returns null or the fileID and complete URL to share this file
|
|
*/
|
|
export const uploadFileToFsWithOnionV4 = async (
|
|
fileContent: ArrayBuffer
|
|
): Promise<{ fileId: number; fileUrl: string } | null> => {
|
|
if (!fileContent || !fileContent.byteLength) {
|
|
return null;
|
|
}
|
|
|
|
const result = await OnionSending.sendBinaryViaOnionV4ToFileServer({
|
|
abortSignal: new AbortController().signal,
|
|
bodyBinary: new Uint8Array(fileContent),
|
|
endpoint: POST_GET_FILE_ENDPOINT,
|
|
method: 'POST',
|
|
});
|
|
|
|
if (!batchGlobalIsSuccess(result)) {
|
|
return null;
|
|
}
|
|
|
|
const fileId = result?.body?.id as number | undefined;
|
|
if (!fileId) {
|
|
return null;
|
|
}
|
|
const fileUrl = `${fileServerURL}${POST_GET_FILE_ENDPOINT}/${fileId}`;
|
|
return {
|
|
fileId,
|
|
fileUrl,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Download a file given the fileId from the fileserver
|
|
* @param fileIdOrCompleteUrl the fileId to download or the completeUrl to the fileitself
|
|
* @returns the data as an Uint8Array or null
|
|
*/
|
|
export const downloadFileFromFileServer = async (
|
|
fileIdOrCompleteUrl: string
|
|
): Promise<ArrayBuffer | null> => {
|
|
let fileId = fileIdOrCompleteUrl;
|
|
if (!fileIdOrCompleteUrl) {
|
|
window?.log?.warn('Empty url to download for fileserver');
|
|
return null;
|
|
}
|
|
|
|
if (fileIdOrCompleteUrl.lastIndexOf('/') >= 0) {
|
|
fileId = fileId.substring(fileIdOrCompleteUrl.lastIndexOf('/') + 1);
|
|
}
|
|
|
|
if (fileId.startsWith('/')) {
|
|
fileId = fileId.substring(1);
|
|
}
|
|
|
|
if (!fileId) {
|
|
window.log.info('downloadFileFromFileServer given empty fileId');
|
|
return null;
|
|
}
|
|
|
|
const urlToGet = `${POST_GET_FILE_ENDPOINT}/${fileId}`;
|
|
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
|
|
window.log.info(`about to try to download fsv2: "${urlToGet}"`);
|
|
}
|
|
|
|
// this throws if we get a 404 from the file server
|
|
const result = await OnionSending.getBinaryViaOnionV4FromFileServer({
|
|
abortSignal: new AbortController().signal,
|
|
endpoint: urlToGet,
|
|
method: 'GET',
|
|
throwError: true,
|
|
});
|
|
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
|
|
window.log.info(`download fsv2: "${urlToGet} got result:`, JSON.stringify(result));
|
|
}
|
|
if (!result) {
|
|
return null;
|
|
}
|
|
|
|
if (!batchGlobalIsSuccess(result)) {
|
|
window.log.info(
|
|
'download from fileserver failed with status ',
|
|
parseBatchGlobalStatusCode(result)
|
|
);
|
|
return null;
|
|
}
|
|
|
|
const { bodyBinary } = result;
|
|
if (!bodyBinary || !bodyBinary.byteLength) {
|
|
window.log.info('download from fileserver failed with status, empty content downloaded ');
|
|
return null;
|
|
}
|
|
|
|
return bodyBinary.buffer;
|
|
};
|
|
|
|
const parseStatusCodeFromOnionRequestV4 = (
|
|
onionV4Result: OnionV4JSONSnodeResponse | null
|
|
): number | undefined => {
|
|
if (!onionV4Result) {
|
|
return undefined;
|
|
}
|
|
return onionV4Result?.body?.status_code || undefined;
|
|
};
|
|
|
|
/**
|
|
* Fetch the latest desktop release available on github from the fileserver.
|
|
* This call is onion routed and so do not expose our ip to github nor the file server.
|
|
*/
|
|
export const getLatestReleaseFromFileServer = async (
|
|
userEd25519SecretKey: Uint8Array
|
|
): Promise<string | null> => {
|
|
const sigTimestampSeconds = GetNetworkTime.getNowWithNetworkOffsetSeconds();
|
|
const blindedPkHex = await BlindingActions.blindVersionPubkey({
|
|
ed25519SecretKey: userEd25519SecretKey,
|
|
});
|
|
const signature = await BlindingActions.blindVersionSign({
|
|
ed25519SecretKey: userEd25519SecretKey,
|
|
sigTimestampSeconds,
|
|
});
|
|
const body = {
|
|
'X-FS-Pubkey': blindedPkHex,
|
|
'X-FS-Timestamp': `${sigTimestampSeconds}`,
|
|
'X-FS-Signature': fromUInt8ArrayToBase64(signature),
|
|
};
|
|
const result = await OnionSending.sendJsonViaOnionV4ToFileServer({
|
|
abortSignal: new AbortController().signal,
|
|
endpoint: RELEASE_VERSION_ENDPOINT,
|
|
method: 'GET',
|
|
stringifiedBody: JSON.stringify(body),
|
|
});
|
|
|
|
if (!batchGlobalIsSuccess(result) || parseStatusCodeFromOnionRequestV4(result) !== 200) {
|
|
return null;
|
|
}
|
|
|
|
// we should probably change the logic of sendOnionRequestNoRetries to not have all those levels
|
|
const latestVersionWithV = (result?.body as any)?.result;
|
|
if (!latestVersionWithV) {
|
|
return null;
|
|
}
|
|
return latestVersionWithV;
|
|
};
|