@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
229 lines (224 loc) • 7.55 kB
JavaScript
;
import { create } from 'zustand';
const initialState = {
accounts: {},
accountOrder: [],
accountsArray: [],
loading: false,
loadingSessionIds: new Set(),
error: null
};
// Helper: Build accounts array from accounts map and order
const buildAccountsArray = (accounts, order) => {
const result = [];
for (const id of order) {
const account = accounts[id];
if (account) result.push(account);
}
return result;
};
// Helper: Create QuickAccount from user data
const createQuickAccount = (sessionId, userData, existingAccount, oxyServices) => {
const displayName = userData.name?.full || userData.name?.first || userData.username || 'Account';
const userId = userData.id || userData._id?.toString();
// Preserve existing avatarUrl if avatar hasn't changed (prevents image reload)
let avatarUrl;
if (existingAccount && existingAccount.avatar === userData.avatar && existingAccount.avatarUrl) {
avatarUrl = existingAccount.avatarUrl; // Reuse existing URL
} else if (userData.avatar && oxyServices) {
avatarUrl = oxyServices.getFileDownloadUrl(userData.avatar, 'thumb');
}
return {
sessionId,
userId,
username: userData.username || '',
displayName,
avatar: userData.avatar,
avatarUrl
};
};
export const useAccountStore = create((set, get) => ({
...initialState,
setAccounts: accounts => set(state => {
const accountMap = {};
const order = [];
const seenSessionIds = new Set();
for (const account of accounts) {
if (seenSessionIds.has(account.sessionId)) continue;
seenSessionIds.add(account.sessionId);
accountMap[account.sessionId] = account;
order.push(account.sessionId);
}
const accountsArray = buildAccountsArray(accountMap, order);
const sameOrder = order.length === state.accountOrder.length && order.every((id, i) => id === state.accountOrder[i]);
const sameAccounts = sameOrder && order.every(id => {
const existing = state.accounts[id];
const newAccount = accountMap[id];
return existing && existing.sessionId === newAccount.sessionId && existing.userId === newAccount.userId && existing.avatar === newAccount.avatar && existing.avatarUrl === newAccount.avatarUrl;
});
if (sameAccounts) return {};
return {
accounts: accountMap,
accountOrder: order,
accountsArray
};
}),
addAccount: account => set(state => {
// Check if account with same sessionId exists
if (state.accounts[account.sessionId]) {
// Update existing
const existing = state.accounts[account.sessionId];
if (existing.avatar === account.avatar && existing.avatarUrl === account.avatarUrl) {
return {}; // No change
}
const newAccounts = {
...state.accounts,
[account.sessionId]: account
};
return {
accounts: newAccounts,
accountsArray: buildAccountsArray(newAccounts, state.accountOrder)
};
}
const newAccounts = {
...state.accounts,
[account.sessionId]: account
};
const newOrder = [account.sessionId, ...state.accountOrder];
return {
accounts: newAccounts,
accountOrder: newOrder,
accountsArray: buildAccountsArray(newAccounts, newOrder)
};
}),
updateAccount: (sessionId, updates) => set(state => {
const existing = state.accounts[sessionId];
if (!existing) return {};
const updated = {
...existing,
...updates
};
if (existing.avatar === updated.avatar && existing.avatarUrl === updated.avatarUrl) {
return {}; // No change
}
const newAccounts = {
...state.accounts,
[sessionId]: updated
};
return {
accounts: newAccounts,
accountsArray: buildAccountsArray(newAccounts, state.accountOrder)
};
}),
removeAccount: sessionId => set(state => {
if (!state.accounts[sessionId]) return {};
const {
[sessionId]: _removed,
...rest
} = state.accounts;
const newOrder = state.accountOrder.filter(id => id !== sessionId);
return {
accounts: rest,
accountOrder: newOrder,
accountsArray: buildAccountsArray(rest, newOrder)
};
}),
moveAccountToTop: sessionId => set(state => {
if (!state.accounts[sessionId]) return {};
const filtered = state.accountOrder.filter(id => id !== sessionId);
const newOrder = [sessionId, ...filtered];
return {
accountOrder: newOrder,
accountsArray: buildAccountsArray(state.accounts, newOrder)
};
}),
setLoading: loading => set({
loading
}),
setLoadingSession: (sessionId, loading) => set(state => {
const newSet = new Set(state.loadingSessionIds);
if (loading) {
newSet.add(sessionId);
} else {
newSet.delete(sessionId);
}
return {
loadingSessionIds: newSet
};
}),
setError: error => set({
error
}),
loadAccounts: async (sessionIds, oxyServices, existingAccounts = [], preserveOrder = true) => {
const state = get();
const uniqueSessionIds = Array.from(new Set(sessionIds));
if (uniqueSessionIds.length === 0) {
get().setAccounts([]);
return;
}
const existingMap = new Map(existingAccounts.map(a => [a.sessionId, a]));
for (const account of Object.values(state.accounts)) {
existingMap.set(account.sessionId, account);
}
const missingSessionIds = uniqueSessionIds.filter(id => !existingMap.has(id));
if (missingSessionIds.length === 0) {
const ordered = uniqueSessionIds.map(id => existingMap.get(id)).filter(acc => acc !== undefined);
get().setAccounts(ordered);
return;
}
if (state.loading) {
return;
}
set({
loading: true,
error: null
});
try {
const batchResults = await oxyServices.getUsersBySessions(missingSessionIds);
const accountMap = new Map();
for (const {
sessionId,
user: userData
} of batchResults) {
if (userData && !accountMap.has(sessionId)) {
const existing = existingMap.get(sessionId);
accountMap.set(sessionId, createQuickAccount(sessionId, userData, existing, oxyServices));
}
}
for (const [sessionId, account] of accountMap) {
existingMap.set(sessionId, account);
}
const orderToUse = preserveOrder ? uniqueSessionIds : [...uniqueSessionIds, ...state.accountOrder];
const seen = new Set();
const ordered = [];
for (const sessionId of orderToUse) {
if (seen.has(sessionId)) continue;
seen.add(sessionId);
const account = existingMap.get(sessionId);
if (account) ordered.push(account);
}
get().setAccounts(ordered);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to load accounts';
if (__DEV__) {
console.error('AccountStore: Failed to load accounts:', error);
}
set({
error: errorMessage
});
} finally {
set({
loading: false
});
}
},
reset: () => set(initialState)
}));
// Selectors for performance - return cached array to prevent infinite loops
export const useAccounts = () => {
return useAccountStore(state => state.accountsArray);
};
export const useAccountLoading = () => useAccountStore(s => s.loading);
export const useAccountError = () => useAccountStore(s => s.error);
export const useAccountLoadingSession = sessionId => useAccountStore(s => s.loadingSessionIds.has(sessionId));
//# sourceMappingURL=accountStore.js.map