refactor untils

This commit is contained in:
pa
2026-03-10 21:40:52 +09:00
parent 1dfd0bf54c
commit fe176f22ff
36 changed files with 1062 additions and 628 deletions

View File

@@ -0,0 +1,194 @@
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';
import {
useAuthStore,
useAvatarStore,
useInstanceStore,
useWorldStore
} from '../stores';
import {
extractFileId,
extractFileVersion,
extractVariantVersion
} from '../shared/utils/fileUtils';
import { compareUnityVersion } from '../shared/utils/avatar';
import { queryRequest } from '../api';
async function deleteVRChatCache(ref) {
const authStore = useAuthStore();
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
let assetUrl = '';
let variant = '';
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
const unityPackage = ref.unityPackages[i];
if (
unityPackage.variant &&
unityPackage.variant !== 'standard' &&
unityPackage.variant !== 'security'
) {
continue;
}
if (
unityPackage.platform === 'standalonewindows' &&
compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
) {
assetUrl = unityPackage.assetUrl;
if (!unityPackage.variant || unityPackage.variant === 'standard') {
variant = 'security';
} else {
variant = unityPackage.variant;
}
break;
}
}
const id = extractFileId(assetUrl);
const version = parseInt(extractFileVersion(assetUrl), 10);
const variantVersion = parseInt(extractVariantVersion(assetUrl), 10);
await AssetBundleManager.DeleteCache(id, version, variant, variantVersion);
}
/**
*
* @param {object} ref
* @returns
*/
async function checkVRChatCache(ref) {
if (!ref.unityPackages) {
return { Item1: -1, Item2: false, Item3: '' };
}
const authStore = useAuthStore();
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
let assetUrl = '';
let variant = '';
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
const unityPackage = ref.unityPackages[i];
if (unityPackage.variant && unityPackage.variant !== 'security') {
continue;
}
if (
unityPackage.platform === 'standalonewindows' &&
compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
) {
assetUrl = unityPackage.assetUrl;
if (!unityPackage.variant || unityPackage.variant === 'standard') {
variant = 'security';
} else {
variant = unityPackage.variant;
}
break;
}
}
if (!assetUrl) {
assetUrl = ref.assetUrl;
}
const id = extractFileId(assetUrl);
const version = parseInt(extractFileVersion(assetUrl), 10);
const variantVersion = parseInt(extractVariantVersion(assetUrl), 10);
if (!id || !version) {
return { Item1: -1, Item2: false, Item3: '' };
}
try {
return AssetBundleManager.CheckVRChatCache(
id,
version,
variant,
variantVersion
);
} catch (err) {
console.error('Failed reading VRChat cache size:', err);
toast.error(`Failed reading VRChat cache size: ${err}`);
return { Item1: -1, Item2: false, Item3: '' };
}
}
/**
*
* @param {object} ref
* @returns {Promise<object>}
*/
async function getBundleDateSize(ref) {
const authStore = useAuthStore();
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
const avatarStore = useAvatarStore();
const { avatarDialog } = storeToRefs(avatarStore);
const worldStore = useWorldStore();
const { worldDialog } = storeToRefs(worldStore);
const instanceStore = useInstanceStore();
const { currentInstanceWorld, currentInstanceLocation } =
storeToRefs(instanceStore);
const bundleJson = {};
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
const unityPackage = ref.unityPackages[i];
if (!unityPackage) {
continue;
}
if (
unityPackage.variant &&
unityPackage.variant !== 'standard' &&
unityPackage.variant !== 'security'
) {
continue;
}
if (
!compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
) {
continue;
}
const platform = unityPackage.platform;
if (bundleJson[platform]) {
continue;
}
const assetUrl = unityPackage.assetUrl;
const fileId = extractFileId(assetUrl);
const version = parseInt(extractFileVersion(assetUrl), 10);
let variant = '';
if (!unityPackage.variant || unityPackage.variant === 'standard') {
variant = 'security';
} else {
variant = unityPackage.variant;
}
if (!fileId || !version) {
continue;
}
const args = await queryRequest.fetch('fileAnalysis', {
fileId,
version,
variant
});
if (!args?.json?.success) {
continue;
}
const json = args.json;
if (typeof json.fileSize !== 'undefined') {
json._fileSize = `${(json.fileSize / 1048576).toFixed(2)} MB`;
}
if (typeof json.uncompressedSize !== 'undefined') {
json._uncompressedSize = `${(json.uncompressedSize / 1048576).toFixed(2)} MB`;
}
if (typeof json.avatarStats?.totalTextureUsage !== 'undefined') {
json._totalTextureUsage = `${(json.avatarStats.totalTextureUsage / 1048576).toFixed(2)} MB`;
}
bundleJson[platform] = json;
if (avatarDialog.value.id === ref.id) {
// update avatar dialog
avatarDialog.value.fileAnalysis[platform] = json;
}
// update world dialog
if (worldDialog.value.id === ref.id) {
worldDialog.value.fileAnalysis[platform] = json;
}
// update player list
if (currentInstanceLocation.value.worldId === ref.id) {
currentInstanceWorld.value.fileAnalysis[platform] = json;
}
}
return bundleJson;
}
export { deleteVRChatCache, checkVRChatCache, getBundleDateSize };

View File

@@ -0,0 +1,106 @@
import { useAppearanceSettingsStore } from '../stores';
function padZero(num) {
return String(num).padStart(2, '0');
}
function toIsoLong(date) {
const y = date.getFullYear();
const m = padZero(date.getMonth() + 1);
const d = padZero(date.getDate());
const hh = padZero(date.getHours());
const mm = padZero(date.getMinutes());
const ss = padZero(date.getSeconds());
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
}
function toLocalShort(date, dateFormat, hour12) {
return date
.toLocaleDateString(dateFormat, {
month: '2-digit',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
hourCycle: hour12 ? 'h12' : 'h23'
})
.replace(' AM', 'am')
.replace(' PM', 'pm')
.replace(',', '');
}
function toLocalLong(date, dateFormat, hour12) {
return date.toLocaleDateString(dateFormat, {
month: '2-digit',
day: '2-digit',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hourCycle: hour12 ? 'h12' : 'h23'
});
}
function toLocalTime(date, dateFormat, hour12) {
return date.toLocaleTimeString(dateFormat, {
hour: 'numeric',
minute: 'numeric',
hourCycle: hour12 ? 'h12' : 'h23'
});
}
function toLocalDate(date, dateFormat) {
return date.toLocaleDateString(dateFormat, {
month: '2-digit',
day: '2-digit',
year: 'numeric'
});
}
/**
* @param {string} dateStr
* @param {'long'|'short'|'time'|'date'} format
* @returns {string}
*/
function formatDateFilter(dateStr, format) {
const appearance = useAppearanceSettingsStore();
const {
dtIsoFormat: isoFormat,
dtHour12: hour12,
currentCulture
} = appearance;
if (!dateStr) {
return '-';
}
const dt = new Date(dateStr);
if (isNaN(dt.getTime())) {
return '-';
}
let dateFormat = 'en-gb';
if (!isoFormat && currentCulture) {
dateFormat = currentCulture;
}
if (dateFormat.length > 4 && dateFormat[4] === '_') {
dateFormat = dateFormat.slice(0, 4);
}
if (isoFormat && format === 'long') {
return toIsoLong(dt);
} else if (format === 'long') {
return toLocalLong(dt, dateFormat, hour12);
} else if (format === 'short') {
return toLocalShort(dt, dateFormat, hour12);
} else if (format === 'time') {
return toLocalTime(dt, dateFormat, hour12);
} else if (format === 'date') {
return toLocalDate(dt, dateFormat);
} else {
console.warn(`Unknown date format: ${format}`);
}
return '-';
}
export { formatDateFilter };

View File

@@ -1,7 +1,7 @@
import { toast } from 'vue-sonner';
import { AppDebug } from '../services/appConfig';
import { migrateMemos } from '../shared/utils';
import { migrateMemos } from './memoCoordinator';
import { reconnectWebSocket } from '../services/websocket';
import { useAuthStore } from '../stores/auth';
import { useFriendStore } from '../stores/friend';

View File

@@ -0,0 +1,198 @@
import { toast } from 'vue-sonner';
import { $throw } from '../services/request';
import { AppDebug } from '../services/appConfig.js';
import { extractFileId } from '../shared/utils';
import { imageRequest } from '../api';
function resolveMessage(message) {
if (typeof message === 'function') {
return message();
}
return message;
}
function getInputElement(selector) {
if (!selector) {
return null;
}
if (typeof selector === 'function') {
return selector();
}
if (typeof selector === 'string') {
return document.querySelector(selector);
}
return selector;
}
export function handleImageUploadInput(event, options = {}) {
const {
inputSelector,
// 20MB
maxSize = 20000000,
acceptPattern = /image.*/,
tooLargeMessage,
invalidTypeMessage,
onClear
} = options;
const clearInput = () => {
onClear?.();
const input = getInputElement(inputSelector);
if (input) {
input.value = '';
}
};
const files = event?.target?.files || event?.dataTransfer?.files;
if (!files || files.length === 0) {
clearInput();
return { file: null, clearInput };
}
const file = files[0];
if (file.size >= maxSize) {
if (tooLargeMessage) {
toast.error(resolveMessage(tooLargeMessage));
}
clearInput();
return { file: null, clearInput };
}
let acceptRegex = null;
if (acceptPattern) {
acceptRegex =
acceptPattern instanceof RegExp
? acceptPattern
: new RegExp(acceptPattern);
}
if (acceptRegex && !acceptRegex.test(file.type)) {
if (invalidTypeMessage) {
toast.error(resolveMessage(invalidTypeMessage));
}
clearInput();
return { file: null, clearInput };
}
return { file, clearInput };
}
/**
* @param {string} base64Data - base64 encoded image
* @returns {Promise<string>} resized base64 encoded image
*/
export async function resizeImageToFitLimits(base64Data) {
// frontend limit check = 20MB
return AppApi.ResizeImageToFitLimits(base64Data);
}
/**
* Upload image through AWS
* @param {'avatar'|'world'} type
* @param {object} opts
* @param {string} opts.entityId - avatar or world id
* @param {string} opts.imageUrl - current imageUrl on the entity
* @param {string} opts.base64File - base64 encoded image data
* @param {Blob} opts.blob - the original blob (used for file size)
*/
export async function uploadImageLegacy(
type,
{ entityId, imageUrl, base64File, blob }
) {
const apiMap = {
avatar: {
uploadImage: imageRequest.uploadAvatarImage,
fileStart: imageRequest.uploadAvatarImageFileStart,
fileFinish: imageRequest.uploadAvatarImageFileFinish,
sigStart: imageRequest.uploadAvatarImageSigStart,
sigFinish: imageRequest.uploadAvatarImageSigFinish,
setImage: imageRequest.setAvatarImage
},
world: {
uploadImage: imageRequest.uploadWorldImage,
fileStart: imageRequest.uploadWorldImageFileStart,
fileFinish: imageRequest.uploadWorldImageFileFinish,
sigStart: imageRequest.uploadWorldImageSigStart,
sigFinish: imageRequest.uploadWorldImageSigFinish,
setImage: imageRequest.setWorldImage
}
};
const api = apiMap[type];
const fileMd5 = await AppApi.MD5File(base64File);
const fileSizeInBytes = parseInt(blob.size, 10);
const base64SignatureFile = await AppApi.SignFile(base64File);
const signatureMd5 = await AppApi.MD5File(base64SignatureFile);
const signatureSizeInBytes = parseInt(
await AppApi.FileLength(base64SignatureFile),
10
);
const fileId = extractFileId(imageUrl);
// imageInit
const uploadRes = await api.uploadImage(
{ fileMd5, fileSizeInBytes, signatureMd5, signatureSizeInBytes },
fileId
);
const uploadedFileId = uploadRes.json.id;
const fileVersion =
uploadRes.json.versions[uploadRes.json.versions.length - 1].version;
// imageFileStart
const fileStartRes = await api.fileStart({
fileId: uploadedFileId,
fileVersion
});
// uploadImageFileAWS
const fileAwsRes = await webApiService.execute({
url: fileStartRes.json.url,
uploadFilePUT: true,
fileData: base64File,
fileMIME: 'image/png',
fileMD5: fileMd5
});
if (fileAwsRes.status !== 200) {
$throw(
fileAwsRes.status,
`${type} image upload failed`,
fileStartRes.json.url
);
}
// imageFileFinish
await api.fileFinish({ fileId: uploadedFileId, fileVersion });
// imageSigStart
const sigStartRes = await api.sigStart({
fileId: uploadedFileId,
fileVersion
});
// uploadImageSigAWS
const sigAwsRes = await webApiService.execute({
url: sigStartRes.json.url,
uploadFilePUT: true,
fileData: base64SignatureFile,
fileMIME: 'application/x-rsync-signature',
fileMD5: signatureMd5
});
if (sigAwsRes.status !== 200) {
$throw(
sigAwsRes.status,
`${type} image upload failed`,
sigStartRes.json.url
);
}
// imageSigFinish
await api.sigFinish({ fileId: uploadedFileId, fileVersion });
// imageSet
const newImageUrl = `${AppDebug.endpointDomain}/file/${uploadedFileId}/${fileVersion}/file`;
const setRes = await api.setImage({ id: entityId, imageUrl: newImageUrl });
if (setRes.json.imageUrl !== newImageUrl) {
$throw(0, `${type} image change failed`, newImageUrl);
}
}

View File

@@ -0,0 +1,18 @@
import { instanceRequest } from '../api';
import { parseLocation } from '../shared/utils/locationParser';
/**
*
* @param {object} instance
*/
function refreshInstancePlayerCount(instance) {
const L = parseLocation(instance);
if (L.isRealInstance) {
instanceRequest.getInstance({
worldId: L.worldId,
instanceId: L.instanceId
});
}
}
export { refreshInstancePlayerCount };

View File

@@ -0,0 +1,126 @@
import { useFriendStore, useUserStore } from '../stores';
import { database } from '../services/database';
/**
* @returns {Promise<void>}
*/
async function migrateMemos() {
const json = JSON.parse(await VRCXStorage.GetAll());
for (const line in json) {
if (line.substring(0, 8) === 'memo_usr') {
const userId = line.substring(5);
const memo = json[line];
if (memo) {
await saveUserMemo(userId, memo);
VRCXStorage.Remove(`memo_${userId}`);
}
}
}
}
/**
*
* @param {string} userId
* @returns
*/
async function getUserMemo(userId) {
try {
return await database.getUserMemo(userId);
} catch (err) {
console.error(err);
return {
userId: '',
editedAt: '',
memo: ''
};
}
}
/**
*
* @param {string} id
* @param {string} memo
*/
async function saveUserMemo(id, memo) {
const friendStore = useFriendStore();
const userStore = useUserStore();
if (memo) {
await database.setUserMemo({
userId: id,
editedAt: new Date().toJSON(),
memo
});
} else {
await database.deleteUserMemo(id);
}
const ref = friendStore.friends.get(id);
if (ref) {
ref.memo = String(memo || '');
if (memo) {
const array = memo.split('\n');
ref.$nickName = array[0];
} else {
ref.$nickName = '';
}
userStore.setUserDialogMemo(memo);
}
}
/**
* @returns {Promise<void>}
*/
async function getAllUserMemos() {
const friendStore = useFriendStore();
const memos = await database.getAllUserMemos();
memos.forEach((memo) => {
const ref = friendStore.friends.get(memo.userId);
if (typeof ref !== 'undefined') {
ref.memo = memo.memo;
ref.$nickName = '';
if (memo.memo) {
const array = memo.memo.split('\n');
ref.$nickName = array[0];
}
}
});
}
/**
*
* @param {string} worldId
* @returns
*/
async function getWorldMemo(worldId) {
try {
return await database.getWorldMemo(worldId);
} catch (err) {
console.error(err);
return {
worldId: '',
editedAt: '',
memo: ''
};
}
}
// async function getAvatarMemo(avatarId) {
// try {
// return await database.getAvatarMemoDB(avatarId);
// } catch (err) {
// console.error(err);
// return {
// avatarId: '',
// editedAt: '',
// memo: ''
// };
// }
// }
export {
migrateMemos,
getUserMemo,
saveUserMemo,
getAllUserMemos,
getWorldMemo
// getAvatarMemo
};

View File

@@ -10,12 +10,12 @@ import {
evictMapCache,
extractFileId,
findUserByDisplayName,
getUserMemo,
getWorldName,
isRealInstance,
parseLocation,
sanitizeUserJson
} from '../shared/utils';
import { getUserMemo } from './memoCoordinator';
import {
avatarRequest,
instanceRequest,
@@ -73,7 +73,14 @@ export function applyUser(json) {
const moderationStore = useModerationStore();
const photonStore = usePhotonStore();
const { currentUser, cachedUsers, currentTravelers, customUserTags, state, userDialog } = userStore;
const {
currentUser,
cachedUsers,
currentTravelers,
customUserTags,
state,
userDialog
} = userStore;
let ref = cachedUsers.get(json.id);
let hasPropChanged = false;
@@ -114,10 +121,8 @@ export function applyUser(json) {
if (json.state !== 'online') {
runUpdateFriendFlow(ref.id, json.state);
}
const {
hasPropChanged: _hasPropChanged,
changedProps: _changedProps
} = diffObjectProps(ref, json, arraysMatch);
const { hasPropChanged: _hasPropChanged, changedProps: _changedProps } =
diffObjectProps(ref, json, arraysMatch);
for (const prop in json) {
if (typeof json[prop] !== 'undefined') {
ref[prop] = json[prop];
@@ -235,10 +240,7 @@ export function applyUser(json) {
}
}
if (hasPropChanged) {
if (
changedProps.location &&
changedProps.location[0] !== 'traveling'
) {
if (changedProps.location && changedProps.location[0] !== 'traveling') {
const ts = Date.now();
changedProps.location.push(ts - ref.$location_at);
ref.$location_at = ts;
@@ -286,11 +288,7 @@ export function showUserDialog(userId) {
const D = userDialog;
D.visible = true;
if (isMainDialogOpen && D.id === userId) {
uiStore.setDialogCrumbLabel(
'user',
D.id,
D.ref?.displayName || D.id
);
uiStore.setDialogCrumbLabel('user', D.id, D.ref?.displayName || D.id);
userStore.applyUserDialogLocation(true);
return;
}
@@ -429,8 +427,7 @@ export function showUserDialog(userId) {
D.joinCount = ref1.joinCount;
D.timeSpent = ref1.timeSpent;
}
const displayNameMap =
ref1.previousDisplayNames;
const displayNameMap = ref1.previousDisplayNames;
const userNotifications =
await database.getFriendLogHistoryForUserId(
D.id,
@@ -457,12 +454,10 @@ export function showUserDialog(userId) {
}
D.dateFriendedInfo = dateFriendedInfo;
if (dateFriendedInfo.length > 0) {
const latestFriendedInfo =
dateFriendedInfo[0];
const latestFriendedInfo = dateFriendedInfo[0];
D.unFriended =
latestFriendedInfo.type === 'Unfriend';
D.dateFriended =
latestFriendedInfo.created_at;
D.dateFriended = latestFriendedInfo.created_at;
}
displayNameMap.forEach(
(updated_at, displayName) => {
@@ -473,27 +468,24 @@ export function showUserDialog(userId) {
}
);
});
AppApi.GetVRChatUserModeration(
currentUser.id,
userId
).then((result) => {
D.avatarModeration = result;
if (result === 4) {
D.isHideAvatar = true;
} else if (result === 5) {
D.isShowAvatar = true;
AppApi.GetVRChatUserModeration(currentUser.id, userId).then(
(result) => {
D.avatarModeration = result;
if (result === 4) {
D.isHideAvatar = true;
} else if (result === 5) {
D.isShowAvatar = true;
}
}
});
);
if (!currentUser.hasSharedConnectionsOptOut) {
try {
queryRequest
.fetch('mutualCounts', { userId })
.then((args) => {
if (args.params.userId === D.id) {
D.mutualFriendCount =
args.json.friends;
D.mutualGroupCount =
args.json.groups;
D.mutualFriendCount = args.json.friends;
D.mutualGroupCount = args.json.groups;
}
});
} catch (error) {
@@ -501,8 +493,7 @@ export function showUserDialog(userId) {
}
}
} else {
D.previousDisplayNames =
currentUser.pastDisplayNames;
D.previousDisplayNames = currentUser.pastDisplayNames;
database
.getUserStats(D.ref, inCurrentWorld)
.then((ref1) => {
@@ -673,11 +664,8 @@ export function handleConfig(args) {
if (typeof args.ref?.whiteListedAssetUrls !== 'object') {
console.error('Invalid config whiteListedAssetUrls');
}
AppApi.PopulateImageHosts(
JSON.stringify(args.ref.whiteListedAssetUrls)
);
const languages =
args.ref?.constants?.LANGUAGE?.SPOKEN_LANGUAGE_OPTIONS;
AppApi.PopulateImageHosts(JSON.stringify(args.ref.whiteListedAssetUrls));
const languages = args.ref?.constants?.LANGUAGE?.SPOKEN_LANGUAGE_OPTIONS;
if (!languages) {
return;
}
@@ -1047,10 +1035,7 @@ export function updateAutoStateChange() {
}
const params = { status: newStatus };
if (
withCompany &&
generalSettingsStore.autoStateChangeCompanyDescEnabled
) {
if (withCompany && generalSettingsStore.autoStateChangeCompanyDescEnabled) {
params.statusDescription =
generalSettingsStore.autoStateChangeCompanyDesc;
} else if (

View File

@@ -8,11 +8,11 @@ import {
evictMapCache,
getAvailablePlatforms,
getBundleDateSize,
getWorldMemo,
isRealInstance,
parseLocation,
sanitizeEntityJson
} from '../shared/utils';
import { getWorldMemo } from './memoCoordinator';
import { instanceRequest, queryRequest, worldRequest } from '../api';
import { database } from '../services/database';
import { patchWorldFromEvent } from '../queries';
@@ -118,21 +118,13 @@ export function showWorldDialog(tag, shortName = null, options = {}) {
.then((args) => {
if (D.id === args.ref.id) {
D.ref = args.ref;
uiStore.setDialogCrumbLabel(
'world',
D.id,
D.ref?.name || D.id
);
uiStore.setDialogCrumbLabel('world', D.id, D.ref?.name || D.id);
D.visible = true;
D.loading = false;
D.isFavorite = favoriteStore.getCachedFavoritesByObjectId(
D.id
);
D.isFavorite = favoriteStore.getCachedFavoritesByObjectId(D.id);
if (!D.isFavorite) {
D.isFavorite =
favoriteStore.localWorldFavoritesList.includes(
D.id
);
favoriteStore.localWorldFavoritesList.includes(D.id);
}
let { isPC, isQuest, isIos } = getAvailablePlatforms(
args.ref.unityPackages