feat(web): add spaces, grouped channels and file chat
This commit is contained in:
1 parent
c7d8ef15bc
commit
f6cfb12716
111 files changed
+4371
-506
No files matched your search
@@ -2,13 +2,77 @@ import type { ItemEvent } from '@webdrop/core';
|
||||
import type { Device, FileItem, ShareEvent, WorkspaceEvent } from '../types';
|
||||
import { previewKindOf } from '@/lib/fileKind';
|
||||
import { publish } from './port';
|
||||
export function deviceIdentity(): { name: string; kind: Device['kind'] } {
|
||||
const mobile = /Android|iPhone|iPad|Mobile/i.test(navigator.userAgent);
|
||||
return { name: mobile ? '本机 · 移动设备' : '本机 · 浏览器', kind: mobile ? 'phone' : 'desktop' };
|
||||
interface ClientHints {
|
||||
mobile?: boolean;
|
||||
model?: string;
|
||||
platform?: string;
|
||||
platformVersion?: string;
|
||||
getHighEntropyValues?: (hints: string[]) => Promise<ClientHints>;
|
||||
}
|
||||
|
||||
let clientHintsPromise: Promise<ClientHints> | undefined;
|
||||
|
||||
async function readClientHints(): Promise<ClientHints> {
|
||||
const hints = (navigator as Navigator & { userAgentData?: ClientHints }).userAgentData;
|
||||
if (!hints?.getHighEntropyValues) return hints ?? {};
|
||||
clientHintsPromise ??= hints.getHighEntropyValues(['model', 'platform', 'platformVersion']).catch(() => hints);
|
||||
return clientHintsPromise;
|
||||
}
|
||||
|
||||
function clean(value: string | undefined): string | undefined {
|
||||
const result = value?.replace(/[<>\u0000-\u001f]/g, '').trim().slice(0, 48);
|
||||
return result || undefined;
|
||||
}
|
||||
|
||||
function version(value: string | undefined): string | undefined {
|
||||
return value?.split('.').slice(0, 2).join('.') || undefined;
|
||||
}
|
||||
|
||||
export async function deviceIdentity(endpointSecret: Uint8Array): Promise<{ name: string; kind: Device['kind'] }> {
|
||||
const ua = navigator.userAgent;
|
||||
const hints = await readClientHints();
|
||||
const android = /Android/i.test(ua) || hints.platform === 'Android';
|
||||
const iphone = /iPhone|iPod/i.test(ua);
|
||||
const ipad = /iPad/i.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||
const tablet = ipad || (android && (hints.mobile === false || (!hints.mobile && !/Mobile/i.test(ua))));
|
||||
const mobile = !tablet && (android || iphone || /Mobile/i.test(ua));
|
||||
const kind: Device['kind'] = tablet ? 'tablet' : mobile ? 'phone' : 'desktop';
|
||||
const iosVersion = ua.match(/(?:iPhone )?OS ([\d_]+)/i)?.[1]?.replaceAll('_', '.');
|
||||
const windowsVersion = version(hints.platformVersion);
|
||||
const macVersion = ua.match(/Mac OS X ([\d_]+)/i)?.[1]?.replaceAll('_', '.');
|
||||
const androidVersion = ua.match(/Android ([\d.]+)/i)?.[1];
|
||||
const platformVersion = version(hints.platformVersion);
|
||||
let identity: string;
|
||||
if (clean(hints.model)) {
|
||||
identity = `${clean(hints.model)} (${clean(hints.platform) ?? 'Android'}${platformVersion ? ` ${platformVersion}` : ''})`;
|
||||
} else if (iphone) {
|
||||
identity = `iPhone${iosVersion ? ` (iOS ${iosVersion})` : ''}`;
|
||||
} else if (ipad) {
|
||||
identity = `iPad${iosVersion ? ` (iPadOS ${iosVersion})` : ''}`;
|
||||
} else if (/Windows/i.test(ua) || hints.platform === 'Windows') {
|
||||
identity = `Windows${windowsVersion ? ` ${Number(windowsVersion.split('.')[0]) >= 13 ? '11' : '10'}` : ''}`;
|
||||
} else if (android) {
|
||||
identity = `Android${platformVersion ?? androidVersion ? ` ${platformVersion ?? androidVersion}` : ''}`;
|
||||
} else if (/Macintosh|Mac OS X/i.test(ua) || hints.platform === 'macOS') {
|
||||
identity = `Mac${platformVersion ?? macVersion ? ` (macOS ${platformVersion ?? macVersion})` : ''}`;
|
||||
} else if (/CrOS/i.test(ua) || hints.platform === 'Chrome OS') {
|
||||
identity = 'ChromeOS';
|
||||
} else if (/Linux/i.test(ua) || hints.platform === 'Linux') {
|
||||
identity = 'Linux';
|
||||
} else {
|
||||
identity = '未知设备';
|
||||
}
|
||||
const suffix = Array.from(endpointSecret.subarray(0, 4), (b) => b.toString(16).padStart(2, '0')).join('').toUpperCase();
|
||||
return { name: `${identity} · ${suffix}`, kind };
|
||||
}
|
||||
|
||||
export function deviceInfo(id: string, joinedAt: number, label: string | undefined, selfId: string): Device {
|
||||
return { id, name: label ?? '未知设备', kind: 'desktop', online: id === selfId, self: id === selfId, lastSeen: joinedAt };
|
||||
const name = label ?? '未知设备';
|
||||
const kind: Device['kind'] = /iPhone|iPod/i.test(name) ? 'phone'
|
||||
: /iPad|Galaxy Tab|Pixel Tablet|Android.*Tablet/i.test(name) ? 'tablet'
|
||||
: /Android|Pixel|Galaxy|OnePlus|Xiaomi|Redmi|OPPO|vivo|Huawei/i.test(name) ? 'phone'
|
||||
: 'desktop';
|
||||
return { id, name, kind, self: id === selfId, joinedAt };
|
||||
}
|
||||
export function showRejected(resourceId: string, event: Extract<ItemEvent, { type: 'rejected' }>, share = false): void {
|
||||
if (share) publish(resourceId, { t: 'integrity', message: '分享清单校验失败,内容已拒绝显示。' });
|
||||
|
||||
Reference in new issue
Block a user