142 lines
4.4 KiB
TypeScript
142 lines
4.4 KiB
TypeScript
import type { AccountInfo, SessionUserInfo, SharedSessionData } from '../types';
|
|
|
|
import {
|
|
ACCOUNT_QUERY,
|
|
DATA_BROADCAST_CHANNEL_PREFIX,
|
|
ESTABLISH_BROADCAST_CHANNEL_PREFIX,
|
|
GLOBAL_STATE_CACHE_PREFIX,
|
|
MULTITAB_LOCALSTORAGE_KEY_PREFIX,
|
|
SESSION_ACCOUNT_PREFIX,
|
|
} from '../config';
|
|
import { IS_MULTIACCOUNT_SUPPORTED } from './browser/globalEnvironment';
|
|
|
|
const WORKER_NAME = typeof WorkerGlobalScope !== 'undefined' && globalThis.self instanceof WorkerGlobalScope
|
|
? globalThis.self.name : undefined;
|
|
const WORKER_ACCOUNT_SLOT = WORKER_NAME ? Number(new URLSearchParams(WORKER_NAME).get(ACCOUNT_QUERY)) : undefined;
|
|
|
|
export const ACCOUNT_SLOT = WORKER_ACCOUNT_SLOT || (
|
|
IS_MULTIACCOUNT_SUPPORTED ? getAccountSlot(globalThis.location.href) : undefined
|
|
);
|
|
|
|
export const DATA_BROADCAST_CHANNEL_NAME = `${DATA_BROADCAST_CHANNEL_PREFIX}_${ACCOUNT_SLOT || 1}`;
|
|
export const ESTABLISH_BROADCAST_CHANNEL_NAME = `${ESTABLISH_BROADCAST_CHANNEL_PREFIX}_${ACCOUNT_SLOT || 1}`;
|
|
export const MULTITAB_STORAGE_KEY = `${MULTITAB_LOCALSTORAGE_KEY_PREFIX}_${ACCOUNT_SLOT || 1}`;
|
|
export const GLOBAL_STATE_CACHE_KEY = ACCOUNT_SLOT
|
|
? `${GLOBAL_STATE_CACHE_PREFIX}_${ACCOUNT_SLOT}` : GLOBAL_STATE_CACHE_PREFIX;
|
|
|
|
export function getAccountSlot(url: string) {
|
|
const params = new URL(url).searchParams;
|
|
const slot = params.get(ACCOUNT_QUERY);
|
|
const slotNumber = slot ? Number(slot) : 1;
|
|
if (!slotNumber || Number.isNaN(slotNumber) || slotNumber === 1) return undefined;
|
|
return slotNumber;
|
|
}
|
|
|
|
export function getAccountsInfo() {
|
|
if (!IS_MULTIACCOUNT_SUPPORTED) return {};
|
|
const allKeys = Object.keys(localStorage);
|
|
const allSlots = allKeys.filter((key) => key.startsWith(SESSION_ACCOUNT_PREFIX));
|
|
const accountInfo: Record<number, AccountInfo> = {};
|
|
for (const key of allSlots) {
|
|
const i = Number(key.slice(SESSION_ACCOUNT_PREFIX.length));
|
|
const info = getAccountInfo(i);
|
|
if (info) {
|
|
accountInfo[i] = info;
|
|
}
|
|
}
|
|
return accountInfo;
|
|
}
|
|
|
|
function getAccountInfo(slot: number): AccountInfo | undefined {
|
|
const sessionData = loadSlotSession(slot);
|
|
const {
|
|
userId, avatarUri, color, emojiStatusId, firstName, lastName, isPremium, isTest, phone,
|
|
} = sessionData || {};
|
|
|
|
if (!userId) return undefined;
|
|
|
|
return {
|
|
userId,
|
|
avatarUri,
|
|
color,
|
|
emojiStatusId,
|
|
firstName,
|
|
lastName,
|
|
isPremium,
|
|
isTest,
|
|
phone,
|
|
};
|
|
}
|
|
|
|
export function loadSlotSession(slot: number | undefined): SharedSessionData | undefined {
|
|
try {
|
|
const data = JSON.parse(localStorage.getItem(`${SESSION_ACCOUNT_PREFIX}${slot || 1}`) || '{}') as SharedSessionData;
|
|
if (!data.dcId) return undefined;
|
|
return data;
|
|
} catch (e) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export function storeAccountData(slot: number | undefined, data: Partial<SessionUserInfo>) {
|
|
const currentSlotData = loadSlotSession(slot);
|
|
|
|
if (!currentSlotData) return;
|
|
|
|
const updatedSharedData: SharedSessionData = {
|
|
...currentSlotData,
|
|
...data,
|
|
};
|
|
|
|
if (!updatedSharedData.userId) return;
|
|
|
|
writeSlotSession(slot, updatedSharedData);
|
|
}
|
|
|
|
export function writeSlotSession(slot: number | undefined, data: SharedSessionData) {
|
|
localStorage.setItem(`${SESSION_ACCOUNT_PREFIX}${slot || 1}`, JSON.stringify(data));
|
|
}
|
|
|
|
export function getAccountSlotUrl(slot: number, forLogin?: boolean, isTest?: boolean) {
|
|
const url = new URL(globalThis.location.href);
|
|
if (slot !== 1) {
|
|
url.searchParams.set(ACCOUNT_QUERY, String(slot));
|
|
} else {
|
|
url.searchParams.delete(ACCOUNT_QUERY);
|
|
}
|
|
|
|
if (isTest) {
|
|
url.searchParams.set('test', 'true');
|
|
} else {
|
|
url.searchParams.delete('test');
|
|
}
|
|
|
|
url.hash = forLogin ? 'login' : '';
|
|
return url.toString();
|
|
}
|
|
|
|
// Validate current version across all tabs to avoid conflicts
|
|
if (typeof window === 'object') {
|
|
const versionChannel = new BroadcastChannel('tt-version');
|
|
versionChannel.postMessage({ version: APP_VERSION });
|
|
|
|
versionChannel.addEventListener('message', (event) => {
|
|
const { version } = event.data;
|
|
if (!version) return;
|
|
if (semverCompare(APP_VERSION, version) === -1) {
|
|
window.location.reload();
|
|
}
|
|
|
|
// If incoming version is older, send back the current version
|
|
if (semverCompare(APP_VERSION, version) === 1) {
|
|
versionChannel.postMessage({ version: APP_VERSION });
|
|
}
|
|
});
|
|
}
|
|
|
|
function semverCompare(a: string, b: string) {
|
|
if (a.startsWith(`${b}-`)) return -1;
|
|
if (b.startsWith(`${a}-`)) return 1;
|
|
return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' });
|
|
}
|