Copy the Chatwoot (v4.14.0) frontend runnable subset into frontend/ for customization: - app/javascript/ (Vue SPA: dashboard, widget, sdk, portal, superadmin) - app/views/ (ERB templates for vite-plugin-ruby entrypoint resolution) - app/helpers/, app/assets/ (Rails view helpers, static assets) - enterprise/ (Enterprise edition frontend overlay) - config/vite.json, vite.config.ts, bin/vite (Vite-Rails toolchain) - package.json, pnpm-lock.yaml, tailwind/postcss/eslint configs - Gemfile, Gemfile.lock (vite_rails gem for bin/vite binstub) Excluded Rails backend: controllers, models, services, jobs, mailers, policies, db, lib, spec, public, node_modules. Update references to the new frontend location: - .gitignore: exclude frontend build artifacts (node_modules, tmp, packs), keep frontend/bin/ and frontend/vendor/ via negation - backend/scripts/parity_frontend_smoke.sh: CHATWOOT_DIR default reference/chatwoot -> ../frontend - backend/scripts/parity_frontend_browser_smoke.mjs: same default update - AGENTS.md: add frontend section with Rails/Vite coupling notes - README: architecture tree includes frontend/
51 lines
1.4 KiB
JavaScript
51 lines
1.4 KiB
JavaScript
// Detects the current OS using the modern User-Agent Client Hints API,
|
|
// falling back to userAgent parsing on Safari/Firefox where it is unavailable.
|
|
// Treats iPad on iOS 13+ (which spoofs Macintosh) as iOS via maxTouchPoints.
|
|
|
|
export const OS = Object.freeze({
|
|
MAC: 'macos',
|
|
WINDOWS: 'windows',
|
|
LINUX: 'linux',
|
|
ANDROID: 'android',
|
|
IOS: 'ios',
|
|
UNKNOWN: 'unknown',
|
|
});
|
|
|
|
// navigator.userAgentData.platform → OS constant (lowercased keys)
|
|
const UAD_MAP = {
|
|
macos: OS.MAC,
|
|
windows: OS.WINDOWS,
|
|
linux: OS.LINUX,
|
|
android: OS.ANDROID,
|
|
ios: OS.IOS,
|
|
};
|
|
|
|
export function detectOS() {
|
|
if (typeof navigator === 'undefined') return OS.UNKNOWN;
|
|
|
|
// Trust userAgentData only when it maps to a known OS; otherwise fall
|
|
// through to UA parsing so unmapped values (e.g. "Chrome OS") don't leak.
|
|
const uad = navigator.userAgentData?.platform?.toLowerCase();
|
|
if (uad && UAD_MAP[uad]) return UAD_MAP[uad];
|
|
|
|
const ua = navigator.userAgent || '';
|
|
if (/android/i.test(ua)) return OS.ANDROID;
|
|
if (/iPhone|iPod/.test(ua)) return OS.IOS;
|
|
if (
|
|
/iPad/.test(ua) ||
|
|
(/Macintosh/.test(ua) && (navigator.maxTouchPoints || 0) > 1)
|
|
) {
|
|
return OS.IOS;
|
|
}
|
|
if (/Win/i.test(ua)) return OS.WINDOWS;
|
|
if (/Mac/i.test(ua)) return OS.MAC;
|
|
if (/Linux/i.test(ua)) return OS.LINUX;
|
|
|
|
return OS.UNKNOWN;
|
|
}
|
|
|
|
export const isApple = () => {
|
|
const os = detectOS();
|
|
return os === OS.MAC || os === OS.IOS;
|
|
};
|