[Cast] Use common shared packages

This commit is contained in:
Neeraj Gupta 2024-01-19 14:02:42 +05:30
parent a3bf49e2d2
commit 1e583414d3
19 changed files with 34 additions and 1482 deletions

View file

@ -1,53 +1,5 @@
import * as Sentry from '@sentry/nextjs';
import { getSentryUserID } from 'utils/user';
import { getHasOptedOutOfCrashReports } from 'utils/storage/index';
import { runningInBrowser } from '@ente/shared/apps/browser';
import { getSentryTunnelURL } from '@ente/shared/network/api';
import {
getSentryDSN,
getSentryENV,
getSentryRelease,
getIsSentryEnabled,
} from 'constants/sentry';
import { setupSentry } from '@ente/shared/sentry/config/sentry.config.base';
const HAS_OPTED_OUT_OF_CRASH_REPORTING =
runningInBrowser() && getHasOptedOutOfCrashReports();
if (!HAS_OPTED_OUT_OF_CRASH_REPORTING) {
const SENTRY_DSN = getSentryDSN();
const SENTRY_ENV = getSentryENV();
const SENTRY_RELEASE = getSentryRelease();
const IS_ENABLED = getIsSentryEnabled();
Sentry.init({
dsn: SENTRY_DSN,
enabled: IS_ENABLED,
environment: SENTRY_ENV,
release: SENTRY_RELEASE,
attachStacktrace: true,
autoSessionTracking: false,
tunnel: getSentryTunnelURL(),
beforeSend(event) {
event.request = event.request || {};
const currentURL = new URL(document.location.href);
currentURL.hash = '';
event.request.url = currentURL;
return event;
},
integrations: function (i) {
return i.filter(function (i) {
return i.name !== 'Breadcrumbs';
});
},
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
});
const main = async () => {
Sentry.setUser({ id: await getSentryUserID() });
};
main();
}
const DEFAULT_SENTRY_DSN =
'https://bd3656fc40d74d5e8f278132817963a3@sentry.ente.io/2';
setupSentry(DEFAULT_SENTRY_DSN);

View file

@ -1,28 +0,0 @@
import * as Sentry from '@sentry/nextjs';
import {
getSentryDSN,
getSentryENV,
getSentryRelease,
getIsSentryEnabled,
} from 'constants/sentry';
import { getSentryUserID } from 'utils/user';
const SENTRY_DSN = getSentryDSN();
const SENTRY_ENV = getSentryENV();
const SENTRY_RELEASE = getSentryRelease();
const IS_ENABLED = getIsSentryEnabled();
Sentry.init({
dsn: SENTRY_DSN,
enabled: IS_ENABLED,
environment: SENTRY_ENV,
release: SENTRY_RELEASE,
autoSessionTracking: false,
});
const main = async () => {
Sentry.setUser({ id: await getSentryUserID() });
};
main();

View file

@ -7,6 +7,7 @@ import { getCollectionWithKey } from 'services/collectionService';
import { syncFiles } from 'services/fileService';
import { EnteFile } from 'types/file';
import { downloadFileAsBlob, isRawFileFromFileName } from 'utils/file';
import { logError } from 'utils/sentry';
export const SlideshowContext = createContext<{
showNextSlide: () => void;
@ -36,9 +37,13 @@ export default function Slideshow() {
'targetCollectionKey'
);
const castToken = window.localStorage.getItem('token');
const collection = await getCollectionWithKey(
Number(requestedCollectionID),
requestedCollectionKey
requestedCollectionKey,
'cast',
castToken
);
const files = await syncFiles('normal', [collection], () => {});
@ -48,7 +53,9 @@ export default function Slideshow() {
files.filter((file) => isFileEligibleForCast(file))
);
}
} catch {
} catch (e) {
logError(e, 'error during sync');
alert('error, redirect to home' + e);
router.push('/');
}
};

View file

@ -1,241 +0,0 @@
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import { addLogLine } from 'utils/logging';
import { logError } from 'utils/sentry';
import { ApiError, CustomError, isApiErrorResponse } from 'utils/error';
interface IHTTPHeaders {
[headerKey: string]: any;
}
interface IQueryPrams {
[paramName: string]: any;
}
/**
* Service to manage all HTTP calls.
*/
class HTTPService {
constructor() {
axios.interceptors.response.use(
(response) => Promise.resolve(response),
(error) => {
const config = error.config as AxiosRequestConfig;
if (error.response) {
const response = error.response as AxiosResponse;
let apiError: ApiError;
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (isApiErrorResponse(response.data)) {
const responseData = response.data;
logError(error, 'HTTP Service Error', {
url: config.url,
method: config.method,
xRequestId: response.headers['x-request-id'],
httpStatus: response.status,
errMessage: responseData.message,
errCode: responseData.code,
});
apiError = new ApiError(
responseData.message,
responseData.code,
response.status
);
} else {
if (response.status >= 400 && response.status < 500) {
apiError = new ApiError(
CustomError.CLIENT_ERROR,
'',
response.status
);
} else {
apiError = new ApiError(
CustomError.ServerError,
'',
response.status
);
}
}
logError(apiError, 'HTTP Service Error', {
url: config.url,
method: config.method,
cfRay: response.headers['cf-ray'],
xRequestId: response.headers['x-request-id'],
httpStatus: response.status,
});
throw apiError;
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
addLogLine(
'request failed- no response',
`url: ${config.url}`,
`method: ${config.method}`
);
return Promise.reject(error);
} else {
// Something happened in setting up the request that triggered an Error
addLogLine(
'request failed- axios error',
`url: ${config.url}`,
`method: ${config.method}`
);
return Promise.reject(error);
}
}
);
}
/**
* header object to be append to all api calls.
*/
private headers: IHTTPHeaders = {
'content-type': 'application/json',
};
/**
* Sets the headers to the given object.
*/
public setHeaders(headers: IHTTPHeaders) {
this.headers = {
...this.headers,
...headers,
};
}
/**
* Adds a header to list of headers.
*/
public appendHeader(key: string, value: string) {
this.headers = {
...this.headers,
[key]: value,
};
}
/**
* Removes the given header.
*/
public removeHeader(key: string) {
this.headers[key] = undefined;
}
/**
* Returns axios interceptors.
*/
// eslint-disable-next-line class-methods-use-this
public getInterceptors() {
return axios.interceptors;
}
/**
* Generic HTTP request.
* This is done so that developer can use any functionality
* provided by axios. Here, only the set headers are spread
* over what was sent in config.
*/
public async request(config: AxiosRequestConfig, customConfig?: any) {
// eslint-disable-next-line no-param-reassign
config.headers = {
...this.headers,
...config.headers,
};
if (customConfig?.cancel) {
config.cancelToken = new axios.CancelToken(
(c) => (customConfig.cancel.exec = c)
);
}
return await axios({ ...config, ...customConfig });
}
/**
* Get request.
*/
public get(
url: string,
params?: IQueryPrams,
headers?: IHTTPHeaders,
customConfig?: any
) {
return this.request(
{
headers,
method: 'GET',
params,
url,
},
customConfig
);
}
/**
* Post request
*/
public post(
url: string,
data?: any,
params?: IQueryPrams,
headers?: IHTTPHeaders,
customConfig?: any
) {
return this.request(
{
data,
headers,
method: 'POST',
params,
url,
},
customConfig
);
}
/**
* Put request
*/
public put(
url: string,
data: any,
params?: IQueryPrams,
headers?: IHTTPHeaders,
customConfig?: any
) {
return this.request(
{
data,
headers,
method: 'PUT',
params,
url,
},
customConfig
);
}
/**
* Delete request
*/
public delete(
url: string,
data: any,
params?: IQueryPrams,
headers?: IHTTPHeaders,
customConfig?: any
) {
return this.request(
{
data,
headers,
method: 'DELETE',
params,
url,
},
customConfig
);
}
}
// Creates a Singleton Service.
// This will help me maintain common headers / functionality
// at a central place.
export default new HTTPService();

View file

@ -1,11 +1,6 @@
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import localForage from 'utils/storage/localForage';
import { getActualKey } from '@ente/shared/user';
import { batch } from '@ente/shared/batch';
import { getPublicKey } from './userService';
import HTTPService from './HTTPService';
import { EnteFile } from 'types/file';
import { logError } from 'utils/sentry';
import { CustomError } from 'utils/error';
import {
sortFiles,
@ -71,6 +66,10 @@ import { EncryptedMagicMetadata } from 'types/magicMetadata';
import { VISIBILITY_STATE } from 'types/magicMetadata';
import { getEndpoint } from '@ente/shared/network/api';
import { getToken } from '@ente/shared/storage/localStorage/helpers';
import HTTPService from '@ente/shared/network/HTTPService';
import { logError } from '@ente/shared/sentry';
import { LS_KEYS, getData } from '@ente/shared/storage/localStorage';
import localForage from '@ente/shared/storage/localForage';
const ENDPOINT = getEndpoint();
const COLLECTION_TABLE = 'collections';
@ -347,10 +346,17 @@ export const getCollection = async (
export const getCollectionWithKey = async (
collectionID: number,
key: string
key: string,
tokenType: 'user' | 'cast' = 'user',
castToken?: string
): Promise<Collection> => {
try {
const token = getToken();
let token;
if (tokenType === 'user') {
token = getToken();
} else {
token = castToken!;
}
if (!token) {
return;
}
@ -974,39 +980,6 @@ export const renameCollection = async (
);
};
export const shareCollection = async (
collection: Collection,
withUserEmail: string,
role: string
) => {
try {
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
const token = getToken();
const publicKey: string = await getPublicKey(withUserEmail);
const encryptedKey = await cryptoWorker.boxSeal(
collection.key,
publicKey
);
const shareCollectionRequest = {
collectionID: collection.id,
email: withUserEmail,
role: role,
encryptedKey,
};
await HTTPService.post(
`${ENDPOINT}/collections/share`,
shareCollectionRequest,
null,
{
'X-Auth-Token': token,
}
);
} catch (e) {
logError(e, 'share collection failed ');
throw e;
}
};
export const unshareCollection = async (
collection: Collection,
withUserEmail: string

View file

@ -3,10 +3,8 @@ import {
getRenderableFileURL,
createTypedObjectURL,
} from 'utils/file';
import HTTPService from './HTTPService';
import { EnteFile } from 'types/file';
import { logError } from 'utils/sentry';
import { FILE_TYPE } from 'constants/file';
import { CustomError } from 'utils/error';
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
@ -16,9 +14,10 @@ import { Remote } from 'comlink';
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
import { LimitedCache } from 'types/cache';
import { retryAsyncFunction } from 'utils/network';
import { addLogLine } from 'utils/logging';
import { getToken } from '@ente/shared/storage/localStorage/helpers';
import { getFileURL, getThumbnailURL } from '@ente/shared/network/api';
import HTTPService from '@ente/shared/network/HTTPService';
import { logError } from '@ente/shared/sentry';
class DownloadManager {
private fileObjectURLPromise = new Map<

View file

@ -3,7 +3,7 @@ import localForage from 'utils/storage/localForage';
import { getToken } from '@ente/shared/storage/localStorage/helpers';
import { Collection } from 'types/collection';
import HTTPService from './HTTPService';
import { logError } from 'utils/sentry';
import {
decryptFile,
@ -29,6 +29,7 @@ import {
} from './collectionService';
import { REQUEST_BATCH_SIZE } from 'constants/api';
import { batch } from '@ente/shared/batch';
import HTTPService from '@ente/shared/network/HTTPService';
const ENDPOINT = getEndpoint();
const FILES_TABLE = 'files';

View file

@ -1,31 +0,0 @@
import { logError } from 'utils/sentry';
import HTTPService from './HTTPService';
import { getEndpoint } from '@ente/shared/network/api';
const ENDPOINT = getEndpoint();
export const getKexValue = async (key: string) => {
let resp;
try {
resp = await HTTPService.get(`${ENDPOINT}/kex/get`, {
identifier: key,
});
} catch (e) {
logError(e, 'failed to get kex value');
throw e;
}
return resp.data.wrappedKey;
};
export const setKexValue = async (key: string, value: string) => {
try {
await HTTPService.put(ENDPOINT + '/kex/add', {
customIdentifier: key,
wrappedKey: value,
});
} catch (e) {
logError(e, 'failed to set kex value');
throw e;
}
};

View file

@ -3,10 +3,8 @@ import {
getRenderableFileURL,
createTypedObjectURL,
} from 'utils/file';
import HTTPService from './HTTPService';
import { EnteFile } from 'types/file';
import { logError } from 'utils/sentry';
import { FILE_TYPE } from 'constants/file';
import { CustomError } from 'utils/error';
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
@ -17,6 +15,8 @@ import {
getPublicCollectionFileURL,
getPublicCollectionThumbnailURL,
} from '@ente/shared/network/api';
import HTTPService from '@ente/shared/network/HTTPService';
import { logError } from '@ente/shared/sentry';
class PublicCollectionDownloadManager {
private fileObjectURLPromise = new Map<

View file

@ -1,6 +1,6 @@
import { logError } from '@ente/shared/sentry';
import { ElectronFile } from 'types/upload';
import { convertBytesToHumanReadable } from 'utils/file/size';
import { logError } from 'utils/sentry';
export async function getUint8ArrayView(
file: Blob | ElectronFile

View file

@ -1,757 +0,0 @@
import { PAGES } from 'constants/pages';
import { clearKeys } from 'utils/storage/sessionStorage';
import router from 'next/router';
import { clearData, getData, LS_KEYS } from 'utils/storage/localStorage';
import localForage from 'utils/storage/localForage';
import { getToken } from '@ente/shared/storage/localStorage/helpers';
import HTTPService from './HTTPService';
import {
computeVerifierHelper,
generateLoginSubKey,
generateSRPClient,
getRecoveryKey,
} from 'utils/crypto';
import { logError } from 'utils/sentry';
import { eventBus, Events } from './events';
import {
KeyAttributes,
RecoveryKey,
TwoFactorSecret,
TwoFactorVerificationResponse,
TwoFactorRecoveryResponse,
UserDetails,
DeleteChallengeResponse,
GetRemoteStoreValueResponse,
SetupSRPRequest,
CreateSRPSessionResponse,
UserVerificationResponse,
GetFeatureFlagResponse,
SetupSRPResponse,
CompleteSRPSetupRequest,
CompleteSRPSetupResponse,
SRPSetupAttributes,
SRPAttributes,
UpdateSRPAndKeysRequest,
UpdateSRPAndKeysResponse,
GetSRPAttributesResponse,
} from 'types/user';
import { ApiError, CustomError } from 'utils/error';
import isElectron from 'is-electron';
// import safeStorageService from './electron/safeStorage';
// import { deleteAllCache } from 'utils/storage/cache';
import { B64EncryptionResult } from 'types/crypto';
import { getLocalFamilyData, isPartOfFamily } from 'utils/user/family';
import { AxiosResponse, HttpStatusCode } from 'axios';
import { APPS, getAppName } from 'constants/apps';
import { addLocalLog } from 'utils/logging';
import { convertBase64ToBuffer, convertBufferToBase64 } from 'utils/user';
import { setLocalMapEnabled } from 'utils/storage';
import InMemoryStore, { MS_KEYS } from './InMemoryStore';
import {
getEndpoint,
getFamilyPortalURL,
isDevDeployment,
} from '@ente/shared/network/api';
const ENDPOINT = getEndpoint();
const HAS_SET_KEYS = 'hasSetKeys';
export const sendOtt = (email: string) => {
const appName = getAppName();
return HTTPService.post(`${ENDPOINT}/users/ott`, {
email,
client: appName === APPS.AUTH ? 'totp' : 'web',
});
};
export const getPublicKey = async (email: string) => {
const token = getToken();
const resp = await HTTPService.get(
`${ENDPOINT}/users/public-key`,
{ email },
{
'X-Auth-Token': token,
}
);
return resp.data.publicKey;
};
export const getPaymentToken = async () => {
const token = getToken();
const resp = await HTTPService.get(
`${ENDPOINT}/users/payment-token`,
null,
{
'X-Auth-Token': token,
}
);
return resp.data['paymentToken'];
};
export const getFamiliesToken = async () => {
try {
const token = getToken();
const resp = await HTTPService.get(
`${ENDPOINT}/users/families-token`,
null,
{
'X-Auth-Token': token,
}
);
return resp.data['familiesToken'];
} catch (e) {
logError(e, 'failed to get family token');
throw e;
}
};
export const getRoadmapRedirectURL = async () => {
try {
const token = getToken();
const resp = await HTTPService.get(
`${ENDPOINT}/users/roadmap/v2`,
null,
{
'X-Auth-Token': token,
}
);
return resp.data['url'];
} catch (e) {
logError(e, 'failed to get roadmap url');
throw e;
}
};
export const verifyOtt = (email: string, ott: string) =>
HTTPService.post(`${ENDPOINT}/users/verify-email`, { email, ott });
export const putAttributes = (token: string, keyAttributes: KeyAttributes) =>
HTTPService.put(`${ENDPOINT}/users/attributes`, { keyAttributes }, null, {
'X-Auth-Token': token,
});
export const setRecoveryKey = (token: string, recoveryKey: RecoveryKey) =>
HTTPService.put(`${ENDPOINT}/users/recovery-key`, recoveryKey, null, {
'X-Auth-Token': token,
});
export const logoutUser = async () => {
try {
try {
// ignore server logout result as logoutUser can be triggered before sign up or on token expiry
await _logout();
} catch (e) {
//ignore
}
try {
InMemoryStore.clear();
} catch (e) {
logError(e, 'clear InMemoryStore failed');
}
try {
clearKeys();
} catch (e) {
logError(e, 'clearKeys failed');
}
try {
clearData();
} catch (e) {
logError(e, 'clearData failed');
}
try {
// await deleteAllCache();
} catch (e) {
logError(e, 'deleteAllCache failed');
}
try {
await clearFiles();
} catch (e) {
logError(e, 'clearFiles failed');
}
if (isElectron()) {
try {
// safeStorageService.clearElectronStore();
} catch (e) {
logError(e, 'clearElectronStore failed');
}
}
try {
eventBus.emit(Events.LOGOUT);
} catch (e) {
logError(e, 'Error in logout handlers');
}
router.push(PAGES.ROOT);
} catch (e) {
logError(e, 'logoutUser failed');
}
};
export const clearFiles = async () => {
await localForage.clear();
};
export const isTokenValid = async (token: string) => {
try {
const resp = await HTTPService.get(
`${ENDPOINT}/users/session-validity/v2`,
null,
{
'X-Auth-Token': token,
}
);
try {
if (resp.data[HAS_SET_KEYS] === undefined) {
throw Error('resp.data.hasSetKey undefined');
}
if (!resp.data['hasSetKeys']) {
try {
await putAttributes(
token,
getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES)
);
} catch (e) {
logError(e, 'put attribute failed');
}
}
} catch (e) {
logError(e, 'hasSetKeys not set in session validity response');
}
return true;
} catch (e) {
logError(e, 'session-validity api call failed');
if (
e instanceof ApiError &&
e.httpStatusCode === HttpStatusCode.Unauthorized
) {
return false;
} else {
return true;
}
}
};
export const setupTwoFactor = async () => {
const resp = await HTTPService.post(
`${ENDPOINT}/users/two-factor/setup`,
null,
null,
{
'X-Auth-Token': getToken(),
}
);
return resp.data as TwoFactorSecret;
};
export const enableTwoFactor = async (
code: string,
recoveryEncryptedTwoFactorSecret: B64EncryptionResult
) => {
await HTTPService.post(
`${ENDPOINT}/users/two-factor/enable`,
{
code,
encryptedTwoFactorSecret:
recoveryEncryptedTwoFactorSecret.encryptedData,
twoFactorSecretDecryptionNonce:
recoveryEncryptedTwoFactorSecret.nonce,
},
null,
{
'X-Auth-Token': getToken(),
}
);
};
export const verifyTwoFactor = async (code: string, sessionID: string) => {
const resp = await HTTPService.post(
`${ENDPOINT}/users/two-factor/verify`,
{
code,
sessionID,
},
null
);
return resp.data as TwoFactorVerificationResponse;
};
export const recoverTwoFactor = async (sessionID: string) => {
const resp = await HTTPService.get(`${ENDPOINT}/users/two-factor/recover`, {
sessionID,
});
return resp.data as TwoFactorRecoveryResponse;
};
export const removeTwoFactor = async (sessionID: string, secret: string) => {
const resp = await HTTPService.post(`${ENDPOINT}/users/two-factor/remove`, {
sessionID,
secret,
});
return resp.data as TwoFactorVerificationResponse;
};
export const disableTwoFactor = async () => {
await HTTPService.post(`${ENDPOINT}/users/two-factor/disable`, null, null, {
'X-Auth-Token': getToken(),
});
};
export const getTwoFactorStatus = async () => {
const resp = await HTTPService.get(
`${ENDPOINT}/users/two-factor/status`,
null,
{
'X-Auth-Token': getToken(),
}
);
return resp.data['status'];
};
export const _logout = async () => {
if (!getToken()) return true;
try {
await HTTPService.post(`${ENDPOINT}/users/logout`, null, null, {
'X-Auth-Token': getToken(),
});
return true;
} catch (e) {
logError(e, '/users/logout failed');
return false;
}
};
export const sendOTTForEmailChange = async (email: string) => {
if (!getToken()) {
return null;
}
await HTTPService.post(`${ENDPOINT}/users/ott`, {
email,
client: 'web',
purpose: 'change',
});
};
export const changeEmail = async (email: string, ott: string) => {
if (!getToken()) {
return null;
}
await HTTPService.post(
`${ENDPOINT}/users/change-email`,
{
email,
ott,
},
null,
{
'X-Auth-Token': getToken(),
}
);
};
export const getUserDetailsV2 = async (): Promise<UserDetails> => {
try {
const token = getToken();
const resp = await HTTPService.get(
`${ENDPOINT}/users/details/v2`,
null,
{
'X-Auth-Token': token,
}
);
return resp.data;
} catch (e) {
logError(e, 'failed to get user details v2');
throw e;
}
};
export const getFamilyPortalRedirectURL = async () => {
try {
const jwtToken = await getFamiliesToken();
const isFamilyCreated = isPartOfFamily(getLocalFamilyData());
return `${getFamilyPortalURL()}?token=${jwtToken}&isFamilyCreated=${isFamilyCreated}&redirectURL=${
window.location.origin
}/gallery`;
} catch (e) {
logError(e, 'unable to generate to family portal URL');
throw e;
}
};
export const getAccountDeleteChallenge = async () => {
try {
const token = getToken();
const resp = await HTTPService.get(
`${ENDPOINT}/users/delete-challenge`,
null,
{
'X-Auth-Token': token,
}
);
return resp.data as DeleteChallengeResponse;
} catch (e) {
logError(e, 'failed to get account delete challenge');
throw e;
}
};
export const deleteAccount = async (
challenge: string,
reason: string,
feedback: string
) => {
try {
const token = getToken();
if (!token) {
return;
}
await HTTPService.delete(
`${ENDPOINT}/users/delete`,
{ challenge, reason, feedback },
null,
{
'X-Auth-Token': token,
}
);
} catch (e) {
logError(e, 'deleteAccount api call failed');
throw e;
}
};
// Ensure that the keys in local storage are not malformed by verifying that the
// recoveryKey can be decrypted with the masterKey.
// Note: This is not bullet-proof.
export const validateKey = async () => {
try {
await getRecoveryKey();
return true;
} catch (e) {
await logoutUser();
return false;
}
};
export const getFaceSearchEnabledStatus = async () => {
try {
const token = getToken();
const resp: AxiosResponse<GetRemoteStoreValueResponse> =
await HTTPService.get(
`${ENDPOINT}/remote-store`,
{
key: 'faceSearchEnabled',
defaultValue: false,
},
{
'X-Auth-Token': token,
}
);
return resp.data.value === 'true';
} catch (e) {
logError(e, 'failed to get face search enabled status');
throw e;
}
};
export const updateFaceSearchEnabledStatus = async (newStatus: boolean) => {
try {
const token = getToken();
await HTTPService.post(
`${ENDPOINT}/remote-store/update`,
{
key: 'faceSearchEnabled',
value: newStatus.toString(),
},
null,
{
'X-Auth-Token': token,
}
);
} catch (e) {
logError(e, 'failed to update face search enabled status');
throw e;
}
};
export const syncMapEnabled = async () => {
try {
const status = await getMapEnabledStatus();
setLocalMapEnabled(status);
} catch (e) {
logError(e, 'failed to sync map enabled status');
throw e;
}
};
export const getMapEnabledStatus = async () => {
try {
const token = getToken();
const resp: AxiosResponse<GetRemoteStoreValueResponse> =
await HTTPService.get(
`${ENDPOINT}/remote-store`,
{
key: 'mapEnabled',
defaultValue: false,
},
{
'X-Auth-Token': token,
}
);
return resp.data.value === 'true';
} catch (e) {
logError(e, 'failed to get map enabled status');
throw e;
}
};
export const updateMapEnabledStatus = async (newStatus: boolean) => {
try {
const token = getToken();
await HTTPService.post(
`${ENDPOINT}/remote-store/update`,
{
key: 'mapEnabled',
value: newStatus.toString(),
},
null,
{
'X-Auth-Token': token,
}
);
} catch (e) {
logError(e, 'failed to update map enabled status');
throw e;
}
};
export async function getDisableCFUploadProxyFlag(): Promise<boolean> {
try {
const disableCFUploadProxy =
process.env.NEXT_PUBLIC_DISABLE_CF_UPLOAD_PROXY;
if (isDevDeployment() && typeof disableCFUploadProxy !== 'undefined') {
return disableCFUploadProxy === 'true';
}
const featureFlags = (
await fetch('https://static.ente.io/feature_flags.json')
).json() as GetFeatureFlagResponse;
return featureFlags.disableCFUploadProxy;
} catch (e) {
logError(e, 'failed to get feature flags');
return false;
}
}
export const getSRPAttributes = async (
email: string
): Promise<SRPAttributes | null> => {
try {
const resp = await HTTPService.get(`${ENDPOINT}/users/srp/attributes`, {
email,
});
return (resp.data as GetSRPAttributesResponse).attributes;
} catch (e) {
logError(e, 'failed to get SRP attributes');
return null;
}
};
export const configureSRP = async ({
srpSalt,
srpUserID,
srpVerifier,
loginSubKey,
}: SRPSetupAttributes) => {
try {
const srpConfigureInProgress = InMemoryStore.get(
MS_KEYS.SRP_CONFIGURE_IN_PROGRESS
);
if (srpConfigureInProgress) {
throw Error('SRP configure already in progress');
}
InMemoryStore.set(MS_KEYS.SRP_CONFIGURE_IN_PROGRESS, true);
const srpClient = await generateSRPClient(
srpSalt,
srpUserID,
loginSubKey
);
const srpA = convertBufferToBase64(srpClient.computeA());
addLocalLog(() => `srp a: ${srpA}`);
const token = getToken();
const { setupID, srpB } = await startSRPSetup(token, {
srpA,
srpUserID,
srpSalt,
srpVerifier,
});
srpClient.setB(convertBase64ToBuffer(srpB));
const srpM1 = convertBufferToBase64(srpClient.computeM1());
const { srpM2 } = await completeSRPSetup(token, {
srpM1,
setupID,
});
srpClient.checkM2(convertBase64ToBuffer(srpM2));
} catch (e) {
logError(e, 'srp configure failed');
throw e;
} finally {
InMemoryStore.set(MS_KEYS.SRP_CONFIGURE_IN_PROGRESS, false);
}
};
export const startSRPSetup = async (
token: string,
setupSRPRequest: SetupSRPRequest
): Promise<SetupSRPResponse> => {
try {
const resp = await HTTPService.post(
`${ENDPOINT}/users/srp/setup`,
setupSRPRequest,
null,
{
'X-Auth-Token': token,
}
);
return resp.data as SetupSRPResponse;
} catch (e) {
logError(e, 'failed to post SRP attributes');
throw e;
}
};
export const completeSRPSetup = async (
token: string,
completeSRPSetupRequest: CompleteSRPSetupRequest
) => {
try {
const resp = await HTTPService.post(
`${ENDPOINT}/users/srp/complete`,
completeSRPSetupRequest,
null,
{
'X-Auth-Token': token,
}
);
return resp.data as CompleteSRPSetupResponse;
} catch (e) {
logError(e, 'failed to complete SRP setup');
throw e;
}
};
export const loginViaSRP = async (
srpAttributes: SRPAttributes,
kek: string
): Promise<UserVerificationResponse> => {
try {
const loginSubKey = await generateLoginSubKey(kek);
const srpClient = await generateSRPClient(
srpAttributes.srpSalt,
srpAttributes.srpUserID,
loginSubKey
);
const srpVerifier = computeVerifierHelper(
srpAttributes.srpSalt,
srpAttributes.srpUserID,
loginSubKey
);
addLocalLog(() => `srp verifier: ${srpVerifier}`);
const srpA = srpClient.computeA();
const { srpB, sessionID } = await createSRPSession(
srpAttributes.srpUserID,
convertBufferToBase64(srpA)
);
srpClient.setB(convertBase64ToBuffer(srpB));
const m1 = srpClient.computeM1();
addLocalLog(() => `srp m1: ${convertBufferToBase64(m1)}`);
const { srpM2, ...rest } = await verifySRPSession(
sessionID,
srpAttributes.srpUserID,
convertBufferToBase64(m1)
);
addLocalLog(() => `srp verify session successful,srpM2: ${srpM2}`);
srpClient.checkM2(convertBase64ToBuffer(srpM2));
addLocalLog(() => `srp server verify successful`);
return rest;
} catch (e) {
logError(e, 'srp verify failed');
throw e;
}
};
export const createSRPSession = async (srpUserID: string, srpA: string) => {
try {
const resp = await HTTPService.post(
`${ENDPOINT}/users/srp/create-session`,
{
srpUserID,
srpA,
}
);
return resp.data as CreateSRPSessionResponse;
} catch (e) {
logError(e, 'createSRPSession failed');
throw e;
}
};
export const verifySRPSession = async (
sessionID: string,
srpUserID: string,
srpM1: string
) => {
try {
const resp = await HTTPService.post(
`${ENDPOINT}/users/srp/verify-session`,
{
sessionID,
srpUserID,
srpM1,
},
null
);
return resp.data as UserVerificationResponse;
} catch (e) {
logError(e, 'verifySRPSession failed');
if (
e instanceof ApiError &&
e.httpStatusCode === HttpStatusCode.Forbidden
) {
throw Error(CustomError.INCORRECT_PASSWORD);
} else {
throw e;
}
}
};
export const updateSRPAndKeys = async (
token: string,
updateSRPAndKeyRequest: UpdateSRPAndKeysRequest
): Promise<UpdateSRPAndKeysResponse> => {
const resp = await HTTPService.post(
`${ENDPOINT}/users/srp/update`,
updateSRPAndKeyRequest,
null,
{
'X-Auth-Token': token,
}
);
return resp.data as UpdateSRPAndKeysResponse;
};

View file

@ -1,12 +1,12 @@
import QueueProcessor from 'services/queueProcessor';
import { CustomError } from 'utils/error';
import { retryAsyncFunction } from 'utils/network';
import { logError } from 'utils/sentry';
import { addLogLine } from 'utils/logging';
import { DedicatedConvertWorker } from 'worker/convert.worker';
import { ComlinkWorker } from 'utils/comlink/comlinkWorker';
import { convertBytesToHumanReadable } from 'utils/file/size';
import { getDedicatedConvertWorker } from 'utils/comlink/ComlinkConvertWorker';
import { logError } from '@ente/shared/sentry';
const WORKER_POOL_SIZE = 2;
const MAX_CONVERSION_IN_PARALLEL = 1;

View file

@ -1,75 +0,0 @@
import * as Sentry from '@sentry/nextjs';
import { addLocalLog, addLogLine } from 'utils/logging';
import { getSentryUserID } from 'utils/user';
import InMemoryStore, { MS_KEYS } from 'services/InMemoryStore';
import { getHasOptedOutOfCrashReports } from 'utils/storage';
import { ApiError } from 'utils/error';
export const logError = async (
error: any,
msg: string,
info?: Record<string, unknown>,
skipAddLogLine = false
) => {
const err = errorWithContext(error, msg);
if (!skipAddLogLine) {
if (error instanceof ApiError) {
addLogLine(`error: ${error?.name} ${error?.message}
msg: ${msg} errorCode: ${JSON.stringify(error?.errCode)}
httpStatusCode: ${JSON.stringify(error?.httpStatusCode)} ${
info ? `info: ${JSON.stringify(info)}` : ''
}
${error?.stack}`);
} else {
addLogLine(
`error: ${error?.name} ${error?.message}
msg: ${msg} ${info ? `info: ${JSON.stringify(info)}` : ''}
${error?.stack}`
);
}
}
if (!InMemoryStore.has(MS_KEYS.OPT_OUT_OF_CRASH_REPORTS)) {
const optedOutOfCrashReports = getHasOptedOutOfCrashReports();
InMemoryStore.set(
MS_KEYS.OPT_OUT_OF_CRASH_REPORTS,
optedOutOfCrashReports
);
}
if (InMemoryStore.get(MS_KEYS.OPT_OUT_OF_CRASH_REPORTS)) {
addLocalLog(() => `skipping sentry error: ${error?.name}`);
return;
}
if (isErrorUnnecessaryForSentry(error)) {
return;
}
Sentry.captureException(err, {
level: 'info',
user: { id: await getSentryUserID() },
contexts: {
...(info && {
info: info,
}),
rootCause: { message: error?.message, completeError: error },
},
});
};
// copy of errorWithContext to prevent importing error util
function errorWithContext(originalError: Error, context: string) {
const errorWithContext = new Error(context);
errorWithContext.stack =
errorWithContext.stack.split('\n').slice(2, 4).join('\n') +
'\n' +
originalError.stack;
return errorWithContext;
}
function isErrorUnnecessaryForSentry(error: any) {
if (error?.message?.includes('Network Error')) {
return true;
} else if (error?.status === 401) {
return true;
}
return false;
}

View file

@ -1,40 +0,0 @@
import { Language } from 'constants/locale';
import { getData, LS_KEYS, setData } from './localStorage';
export const isFirstLogin = () =>
getData(LS_KEYS.IS_FIRST_LOGIN)?.status ?? false;
export function setIsFirstLogin(status) {
setData(LS_KEYS.IS_FIRST_LOGIN, { status });
}
export const justSignedUp = () =>
getData(LS_KEYS.JUST_SIGNED_UP)?.status ?? false;
export function setJustSignedUp(status) {
setData(LS_KEYS.JUST_SIGNED_UP, { status });
}
export function getLivePhotoInfoShownCount() {
return getData(LS_KEYS.LIVE_PHOTO_INFO_SHOWN_COUNT)?.count ?? 0;
}
export function setLivePhotoInfoShownCount(count) {
setData(LS_KEYS.LIVE_PHOTO_INFO_SHOWN_COUNT, { count });
}
export function getUserLocale(): Language {
return getData(LS_KEYS.LOCALE)?.value;
}
export function getLocalMapEnabled(): boolean {
return getData(LS_KEYS.MAP_ENABLED)?.value ?? false;
}
export function setLocalMapEnabled(value: boolean) {
setData(LS_KEYS.MAP_ENABLED, { value });
}
export function getHasOptedOutOfCrashReports(): boolean {
return getData(LS_KEYS.OPT_OUT_OF_CRASH_REPORTS)?.value ?? false;
}

View file

@ -1,11 +0,0 @@
import { runningInBrowser } from '@ente/shared/apps/browser';
import localForage from 'localforage';
if (runningInBrowser()) {
localForage.config({
name: 'ente-files',
version: 1.0,
storeName: 'files',
});
}
export default localForage;

View file

@ -1,68 +0,0 @@
import { logError } from 'utils/sentry';
export enum LS_KEYS {
USER = 'user',
SESSION = 'session',
KEY_ATTRIBUTES = 'keyAttributes',
ORIGINAL_KEY_ATTRIBUTES = 'originalKeyAttributes',
SUBSCRIPTION = 'subscription',
FAMILY_DATA = 'familyData',
PLANS = 'plans',
IS_FIRST_LOGIN = 'isFirstLogin',
JUST_SIGNED_UP = 'justSignedUp',
SHOW_BACK_BUTTON = 'showBackButton',
EXPORT = 'export',
AnonymizedUserID = 'anonymizedUserID',
THUMBNAIL_FIX_STATE = 'thumbnailFixState',
LIVE_PHOTO_INFO_SHOWN_COUNT = 'livePhotoInfoShownCount',
LOGS = 'logs',
USER_DETAILS = 'userDetails',
COLLECTION_SORT_BY = 'collectionSortBy',
THEME = 'theme',
WAIT_TIME = 'waitTime',
API_ENDPOINT = 'apiEndpoint',
LOCALE = 'locale',
MAP_ENABLED = 'mapEnabled',
SRP_SETUP_ATTRIBUTES = 'srpSetupAttributes',
SRP_ATTRIBUTES = 'srpAttributes',
OPT_OUT_OF_CRASH_REPORTS = 'optOutOfCrashReports',
CF_PROXY_DISABLED = 'cfProxyDisabled',
}
export const setData = (key: LS_KEYS, value: object) => {
if (typeof localStorage === 'undefined') {
return null;
}
localStorage.setItem(key, JSON.stringify(value));
};
export const removeData = (key: LS_KEYS) => {
if (typeof localStorage === 'undefined') {
return null;
}
localStorage.removeItem(key);
};
export const getData = (key: LS_KEYS) => {
try {
if (
typeof localStorage === 'undefined' ||
typeof key === 'undefined' ||
typeof localStorage.getItem(key) === 'undefined' ||
localStorage.getItem(key) === 'undefined'
) {
return null;
}
const data = localStorage.getItem(key);
return data && JSON.parse(data);
} catch (e) {
logError(e, 'Failed to Parse JSON for key ' + key);
}
};
export const clearData = () => {
if (typeof localStorage === 'undefined') {
return null;
}
localStorage.clear();
};

View file

@ -1,32 +0,0 @@
export enum SESSION_KEYS {
ENCRYPTION_KEY = 'encryptionKey',
KEY_ENCRYPTION_KEY = 'keyEncryptionKey',
}
export const setKey = (key: SESSION_KEYS, value: object) => {
if (typeof sessionStorage === 'undefined') {
return null;
}
sessionStorage.setItem(key, JSON.stringify(value));
};
export const getKey = (key: SESSION_KEYS) => {
if (typeof sessionStorage === 'undefined') {
return null;
}
return JSON.parse(sessionStorage.getItem(key));
};
export const removeKey = (key: SESSION_KEYS) => {
if (typeof sessionStorage === 'undefined') {
return null;
}
sessionStorage.removeItem(key);
};
export const clearKeys = () => {
if (typeof sessionStorage === 'undefined') {
return null;
}
sessionStorage.clear();
};

View file

@ -1,45 +0,0 @@
import { FamilyData, FamilyMember, User } from 'types/user';
import { logError } from 'utils/sentry';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
export function getLocalFamilyData(): FamilyData {
return getData(LS_KEYS.FAMILY_DATA);
}
// isPartOfFamily return true if the current user is part of some family plan
export function isPartOfFamily(familyData: FamilyData): boolean {
return Boolean(
familyData && familyData.members && familyData.members.length > 0
);
}
// hasNonAdminFamilyMembers return true if the admin user has members in his family
export function hasNonAdminFamilyMembers(familyData: FamilyData): boolean {
return Boolean(isPartOfFamily(familyData) && familyData.members.length > 1);
}
export function isFamilyAdmin(familyData: FamilyData): boolean {
const familyAdmin: FamilyMember = getFamilyPlanAdmin(familyData);
const user: User = getData(LS_KEYS.USER);
return familyAdmin.email === user.email;
}
export function getFamilyPlanAdmin(familyData: FamilyData): FamilyMember {
if (isPartOfFamily(familyData)) {
return familyData.members.find((x) => x.isAdmin);
} else {
logError(
Error(
'verify user is part of family plan before calling this method'
),
'invalid getFamilyPlanAdmin call'
);
}
}
export function getTotalFamilyUsage(familyData: FamilyData): number {
return familyData.members.reduce(
(sum, currentMember) => sum + currentMember.usage,
0
);
}

View file

@ -1,52 +0,0 @@
import isElectron from 'is-electron';
import { UserDetails } from 'types/user';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
// import ElectronService from 'services/electron/common';
import { Buffer } from 'buffer';
export function makeID(length) {
let result = '';
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(
Math.floor(Math.random() * charactersLength)
);
}
return result;
}
export async function getSentryUserID() {
if (isElectron()) {
// return await ElectronService.getSentryUserID();
} else {
let anonymizeUserID = getData(LS_KEYS.AnonymizedUserID)?.id;
if (!anonymizeUserID) {
anonymizeUserID = makeID(6);
setData(LS_KEYS.AnonymizedUserID, { id: anonymizeUserID });
}
return anonymizeUserID;
}
}
export function getLocalUserDetails(): UserDetails {
return getData(LS_KEYS.USER_DETAILS)?.value;
}
export const isInternalUser = () => {
const userEmail = getData(LS_KEYS.USER)?.email;
if (!userEmail) return false;
return (
userEmail.endsWith('@ente.io') || userEmail === 'kr.anand619@gmail.com'
);
};
export const convertBufferToBase64 = (buffer: Buffer) => {
return buffer.toString('base64');
};
export const convertBase64ToBuffer = (base64: string) => {
return Buffer.from(base64, 'base64');
};