import type { StringAutoCompletable } from "#Source/type/index.ts" import type { Use } from "./basic.ts" import { useFactory } from "./basic.ts" /** * @description Define built-in and custom operating-system keys. */ export type System = | StringAutoCompletable | "windows" | "macos" | "linux" | "android" | "ios" | "unknown" interface SystemRegistryItem { system: System detect: () => boolean // oxlint-disable-next-line no-explicit-any getSystemContext: () => any // oxlint-disable-next-line no-explicit-any use: Use } const internalSystemRegistry = new Map() /** * @description Check whether a system is registered. */ export const isSystemRegistered = (system: System): boolean => { return internalSystemRegistry.has(system) } /** * @description Register a system detector and context provider. */ export const registerSystem = (item: SystemRegistryItem): void => { internalSystemRegistry.set(item.system, item) } /** * @description Unregister a system by key. */ export const unregisterSystem = (system: System): void => { internalSystemRegistry.delete(system) } /** * @description List all registered system keys. */ export const listSystems = (): System[] => { return Array.from(internalSystemRegistry.keys()) } interface InternalNavigatorWithUserAgentData extends Navigator { userAgentData?: | { platform?: string | undefined } | undefined } interface InternalSystemContextBase { system: SystemName global: typeof globalThis globalThis: typeof globalThis navigator: Navigator | undefined process: NodeJS.Process | undefined importMeta: ImportMeta processPlatform: string | undefined navigatorPlatform: string | undefined userAgentDataPlatform: string | undefined userAgent: string | undefined } const internalGetNavigator = (): InternalNavigatorWithUserAgentData | undefined => { if (typeof navigator !== "undefined" && navigator !== null && typeof navigator === "object") { return globalThis.navigator as InternalNavigatorWithUserAgentData } return undefined } const internalGetProcess = (): NodeJS.Process | undefined => { if (typeof process !== "undefined" && process !== null && typeof process === "object") { return globalThis.process } return undefined } const internalNormalizeValue = (value: string | undefined): string | undefined => { return value?.toLowerCase() } const internalIncludesSome = (value: string | undefined, keywords: string[]): boolean => { if (value === undefined) { return false } return keywords.some((keyword) => value.includes(keyword)) } const internalGetProcessPlatform = (): string | undefined => { return internalNormalizeValue(internalGetProcess()?.platform) } const internalGetNavigatorPlatform = (): string | undefined => { return internalNormalizeValue(internalGetNavigator()?.platform) } const internalGetUserAgentDataPlatform = (): string | undefined => { return internalNormalizeValue(internalGetNavigator()?.userAgentData?.platform) } const internalGetUserAgent = (): string | undefined => { return internalNormalizeValue(internalGetNavigator()?.userAgent) } const internalGetNavigatorMaxTouchPoints = (): number => { const maxTouchPoints = internalGetNavigator()?.maxTouchPoints return typeof maxTouchPoints === "number" ? maxTouchPoints : 0 } const internalGetSystemContext = ( system: SystemName, ): InternalSystemContextBase => { const navigatorObject = internalGetNavigator() const processObject = internalGetProcess() return { system, global: globalThis, globalThis, navigator: navigatorObject, process: processObject, importMeta: import.meta, processPlatform: internalGetProcessPlatform(), navigatorPlatform: internalGetNavigatorPlatform(), userAgentDataPlatform: internalGetUserAgentDataPlatform(), userAgent: internalGetUserAgent(), } } /** * @description Check if the current operating system is Windows. */ export const isWindows = (): boolean => { const processPlatform = internalGetProcessPlatform() if (processPlatform !== undefined) { return processPlatform === "win32" } return ( internalIncludesSome(internalGetUserAgentDataPlatform(), ["windows", "win"]) || internalIncludesSome(internalGetNavigatorPlatform(), ["win"]) || internalIncludesSome(internalGetUserAgent(), ["windows"]) ) } /** * @description Describe context values available when the current system is Windows. */ export interface SystemContextWindows extends InternalSystemContextBase<"windows"> {} /** * @description Return Windows system context values. */ export const getSystemContextWindows = (): SystemContextWindows => { return internalGetSystemContext("windows") } /** * @description Execute logic with Windows system context when available. */ export const useWindows: Use = useFactory(getSystemContextWindows, isWindows) registerSystem({ system: "windows", detect: isWindows, getSystemContext: getSystemContextWindows, use: useWindows, }) /** * @description Check if the current operating system is Android. */ export const isAndroid = (): boolean => { const processPlatform = internalGetProcessPlatform() if (processPlatform !== undefined) { return processPlatform === "android" } return ( internalIncludesSome(internalGetUserAgentDataPlatform(), ["android"]) || internalIncludesSome(internalGetNavigatorPlatform(), ["android"]) || internalIncludesSome(internalGetUserAgent(), ["android"]) ) } /** * @description Describe context values available when the current system is Android. */ export interface SystemContextAndroid extends InternalSystemContextBase<"android"> {} /** * @description Return Android system context values. */ export const getSystemContextAndroid = (): SystemContextAndroid => { return internalGetSystemContext("android") } /** * @description Execute logic with Android system context when available. */ export const useAndroid: Use = useFactory(getSystemContextAndroid, isAndroid) registerSystem({ system: "android", detect: isAndroid, getSystemContext: getSystemContextAndroid, use: useAndroid, }) /** * @description Check if the current operating system is iOS. */ export const isIos = (): boolean => { const processPlatform = internalGetProcessPlatform() if (processPlatform !== undefined) { return processPlatform === "ios" } if (internalIncludesSome(internalGetUserAgentDataPlatform(), ["ios", "iphone", "ipad", "ipod"])) { return true } if (internalIncludesSome(internalGetNavigatorPlatform(), ["iphone", "ipad", "ipod"])) { return true } if (internalGetNavigatorPlatform() === "macintel" && internalGetNavigatorMaxTouchPoints() > 1) { return true } return internalIncludesSome(internalGetUserAgent(), ["iphone", "ipad", "ipod"]) } /** * @description Describe context values available when the current system is iOS. */ export interface SystemContextIos extends InternalSystemContextBase<"ios"> {} /** * @description Return iOS system context values. */ export const getSystemContextIos = (): SystemContextIos => { return internalGetSystemContext("ios") } /** * @description Execute logic with iOS system context when available. */ export const useIos: Use = useFactory(getSystemContextIos, isIos) registerSystem({ system: "ios", detect: isIos, getSystemContext: getSystemContextIos, use: useIos, }) /** * @description Check if the current operating system is macOS. */ export const isMacos = (): boolean => { const processPlatform = internalGetProcessPlatform() if (processPlatform !== undefined) { return processPlatform === "darwin" } if (isIos() === true) { return false } return ( internalIncludesSome(internalGetUserAgentDataPlatform(), ["macos", "mac"]) || internalIncludesSome(internalGetNavigatorPlatform(), ["mac"]) || internalIncludesSome(internalGetUserAgent(), ["macintosh", "mac os x"]) ) } /** * @description Describe context values available when the current system is macOS. */ export interface SystemContextMacos extends InternalSystemContextBase<"macos"> {} /** * @description Return macOS system context values. */ export const getSystemContextMacos = (): SystemContextMacos => { return internalGetSystemContext("macos") } /** * @description Execute logic with macOS system context when available. */ export const useMacos: Use = useFactory(getSystemContextMacos, isMacos) registerSystem({ system: "macos", detect: isMacos, getSystemContext: getSystemContextMacos, use: useMacos, }) /** * @description Check if the current operating system is Linux. */ export const isLinux = (): boolean => { const processPlatform = internalGetProcessPlatform() if (processPlatform !== undefined) { return processPlatform === "linux" } if (isAndroid() === true) { return false } return ( internalIncludesSome(internalGetUserAgentDataPlatform(), ["linux"]) || internalIncludesSome(internalGetNavigatorPlatform(), ["linux"]) || internalIncludesSome(internalGetUserAgent(), ["linux"]) ) } /** * @description Describe context values available when the current system is Linux. */ export interface SystemContextLinux extends InternalSystemContextBase<"linux"> {} /** * @description Return Linux system context values. */ export const getSystemContextLinux = (): SystemContextLinux => { return internalGetSystemContext("linux") } /** * @description Execute logic with Linux system context when available. */ export const useLinux: Use = useFactory(getSystemContextLinux, isLinux) registerSystem({ system: "linux", detect: isLinux, getSystemContext: getSystemContextLinux, use: useLinux, }) /** * @description Check if the current operating system is unknown. */ export const isUnknown = (): boolean => { return getSystemRegistryItem().system === "unknown" } /** * @description Describe context values available when the current system is unknown. */ export interface SystemContextUnknown extends InternalSystemContextBase<"unknown"> {} /** * @description Return unknown system context values. */ export const getSystemContextUnknown = (): SystemContextUnknown => { return internalGetSystemContext("unknown") } /** * @description Execute logic with unknown system context when available. */ export const useUnknown: Use = useFactory(getSystemContextUnknown, isUnknown) registerSystem({ system: "unknown", detect: isUnknown, getSystemContext: getSystemContextUnknown, use: useUnknown, }) /** * @description Get the current operating-system registry item. */ export const getSystemRegistryItem = (): SystemRegistryItem => { // 如果不是其它任何 System,则是 unknown // 这样写可以兼容用户自行注册的 System for (const [name, item] of internalSystemRegistry) { if (name === "unknown") { continue } if (item.detect() === true) { return item } } return internalSystemRegistry.get("unknown")! } /** * @description Get the key of the current operating system. */ export const getSystem = (): SystemRegistryItem["system"] => { return getSystemRegistryItem().system } /** * @description Define detection results for registered systems. */ export type SystemFlags = Record /** * @description Define a function type that resolves all system flags. */ export type GetSystemFlags = () => SystemFlags /** * @description Get the system flags for all registered systems. */ export const getSystemFlags: GetSystemFlags = (): SystemFlags => { const flags = {} as SystemFlags for (const [name, item] of internalSystemRegistry) { flags[name] = item.detect() === true } return flags } /** * @description Check if the current system satisfies the specified system. */ export const isSatisfiesSystem = (system: System): boolean => { const systemRegistryItem = internalSystemRegistry.get(system) if (systemRegistryItem === undefined) { throw new Error(`System "${system}" is not registered.`) } return systemRegistryItem.detect() === true } /** * @description Check if the current system satisfies the specified systems condition. */ export const isSatisfiesSystems = (condition: Partial): boolean => { let result = true Object.keys(condition).forEach((system) => { const systemRegistryItem = internalSystemRegistry.get(system) if (systemRegistryItem === undefined) { throw new Error(`System "${system}" is not registered.`) } if (systemRegistryItem.detect() !== condition[system]) { result = false } }) return result } /** * @description Map system keys to their typed context payloads. */ export interface SystemContexts { windows: SystemContextWindows android: SystemContextAndroid ios: SystemContextIos macos: SystemContextMacos linux: SystemContextLinux unknown: SystemContextUnknown } /** * @description Define handler options for system-conditional execution. */ export type UseSystemsOptions = { // oxlint-disable-next-line no-explicit-any [K in System]?: (context: K extends keyof SystemContexts ? SystemContexts[K] : any) => R } & { default?: (() => R) | undefined } /** * @description Execute a system-specific handler with fallback support. */ export const useSystems = (options: UseSystemsOptions): R => { const systemRegistryItem = getSystemRegistryItem() const system = systemRegistryItem.system if (options[system] !== undefined) { return options[system](systemRegistryItem.getSystemContext()) } if (options.default !== undefined) { return options.default() } throw new Error( `Neither system-specific nor default handler provided for system: ${String(system)}`, ) }