Files
smartenv/ts/smartenv.classes.smartenv.ts

260 lines
7.2 KiB
TypeScript
Raw Normal View History

2022-03-16 16:09:24 +01:00
import * as plugins from './smartenv.plugins.js';
import * as interfaces from './interfaces/index.js';
2017-05-17 15:59:10 +02:00
// interfaces
export interface IEnvObject {
2019-06-17 08:46:28 +02:00
name: string;
value: string;
2017-05-17 15:59:10 +02:00
}
2019-06-17 08:46:28 +02:00
/**
* Smartenv class that makes it easy
*/
2017-05-17 15:59:10 +02:00
export class Smartenv {
2020-09-29 18:38:21 +00:00
public async getEnvAwareModule(optionsArg: {
nodeModuleName: string;
webUrlArg: string;
getFunction: () => any;
}) {
if (this.isNode) {
2022-12-28 19:43:48 +01:00
const moduleResult = await this.getSafeNodeModule(optionsArg.nodeModuleName);
2020-09-29 18:38:21 +00:00
return moduleResult;
} else if (this.isBrowser) {
2022-12-28 19:43:48 +01:00
const moduleResult = await this.getSafeWebModule(
optionsArg.webUrlArg,
optionsArg.getFunction
);
return moduleResult;
2020-09-29 18:38:21 +00:00
} else {
console.error('platform for loading not supported by smartenv');
}
}
2023-11-09 16:37:52 +01:00
public async getSafeNodeModule<T = any>(moduleNameArg: string, runAfterFunc?: (moduleArg: T) => Promise<any>): Promise<T> {
if (!this.isNode && !this.isDeno && !this.isBun) {
console.error(`You tried to load a server module in a wrong context: ${moduleNameArg}. This does not throw.`);
2020-09-29 17:49:33 +00:00
return;
}
2020-06-25 23:00:32 +00:00
// tslint:disable-next-line: function-constructor
2023-11-09 16:20:20 +01:00
const returnValue: T = await (new Function(`return import('${moduleNameArg}')`)() as Promise<T>);
if (runAfterFunc) {
await runAfterFunc(returnValue);
}
return returnValue;
2020-06-25 23:00:32 +00:00
}
2020-09-29 17:49:33 +00:00
public loadedScripts: string[] = [];
2020-09-29 18:38:21 +00:00
public async getSafeWebModule(urlArg: string, getFunctionArg: () => any) {
2020-09-29 17:49:33 +00:00
if (!this.isBrowser) {
console.error('You tried to load a web module in a wrong context');
return;
}
2022-12-28 19:43:48 +01:00
2020-09-29 17:49:33 +00:00
if (this.loadedScripts.includes(urlArg)) {
2020-10-13 20:09:15 +00:00
return getFunctionArg();
2020-09-29 17:49:33 +00:00
} else {
this.loadedScripts.push(urlArg);
}
2022-12-28 19:43:48 +01:00
2020-09-29 17:49:33 +00:00
const done = plugins.smartpromise.defer();
if (globalThis.importScripts) {
globalThis.importScripts(urlArg);
done.resolve();
} else {
const script = document.createElement('script');
script.onload = () => {
done.resolve();
};
script.src = urlArg;
document.head.appendChild(script);
}
await done.promise;
2020-09-29 18:38:21 +00:00
return getFunctionArg();
2020-09-29 17:49:33 +00:00
}
public get runtimeEnv(): interfaces.TRuntimeType {
// Check Deno first (most distinctive)
if (typeof globalThis.Deno !== 'undefined' &&
typeof (globalThis as any).Deno?.version !== 'undefined') {
return 'deno';
}
// Check Bun second (most distinctive)
if (typeof globalThis.Bun !== 'undefined' &&
typeof (globalThis as any).Bun?.version !== 'undefined') {
return 'bun';
}
// Check Node.js (be explicit about versions to avoid Deno/Bun false positives)
if (typeof globalThis.process !== 'undefined' &&
typeof (globalThis as any).process?.versions?.node !== 'undefined') {
2019-06-17 08:46:28 +02:00
return 'node';
}
// Check Browser (default fallback)
if (typeof globalThis.window !== 'undefined' &&
typeof (globalThis as any).document !== 'undefined') {
2022-05-28 23:22:31 +02:00
return 'browser';
}
// Safe fallback
return 'browser';
}
2019-08-22 00:20:10 +02:00
public get isBrowser(): boolean {
return this.runtimeEnv === 'browser';
}
public get isNode(): boolean {
return this.runtimeEnv === 'node';
}
public get isDeno(): boolean {
return this.runtimeEnv === 'deno';
}
public get isBun(): boolean {
return this.runtimeEnv === 'bun';
}
2019-08-22 00:20:10 +02:00
public get userAgent(): string {
2019-06-17 08:46:28 +02:00
if (this.isBrowser) {
// make sure we are in Browser
return navigator.userAgent;
} else {
2019-06-17 08:46:28 +02:00
return 'undefined';
}
}
public get nodeVersion(): string {
if (this.isNode) {
return process.version;
}
return 'undefined';
}
public get denoVersion(): string {
if (this.isDeno) {
return (globalThis as any).Deno.version.deno;
}
return 'undefined';
}
public get bunVersion(): string {
if (this.isBun) {
return (globalThis as any).Bun.version;
}
return 'undefined';
}
/**
* Load a module only if the current runtime matches the target runtime(s)
* @param target - Single runtime, array of runtimes, or 'server' for all server-side runtimes
* @param moduleNameOrUrl - Module name (for Node/Deno/Bun) or URL (for browser)
* @param getFunction - Optional function to retrieve the module in browser context
* @returns The loaded module or undefined if runtime doesn't match
*/
public async getSafeModuleFor<T = any>(
target: interfaces.TRuntimeTarget | interfaces.TRuntimeTarget[],
moduleNameOrUrl: string,
getFunction?: () => any
): Promise<T | undefined> {
// Normalize target to array
let targetRuntimes: interfaces.TRuntimeType[];
if (Array.isArray(target)) {
// Expand 'server' if present in array
targetRuntimes = target.flatMap(t =>
t === 'server' ? ['node', 'deno', 'bun'] as interfaces.TRuntimeType[] : [t as interfaces.TRuntimeType]
);
} else if (target === 'server') {
targetRuntimes = ['node', 'deno', 'bun'];
} else {
targetRuntimes = [target];
}
// Check if current runtime matches any target
if (!targetRuntimes.includes(this.runtimeEnv)) {
console.warn(
`Module "${moduleNameOrUrl}" requested for runtime(s) [${targetRuntimes.join(', ')}] ` +
`but current runtime is "${this.runtimeEnv}". Skipping load.`
);
return undefined;
}
// Load based on current runtime
if (this.isNode || this.isDeno || this.isBun) {
// Server-side runtimes use dynamic import
const moduleResult = await this.getSafeNodeModule<T>(moduleNameOrUrl);
return moduleResult;
} else if (this.isBrowser) {
if (!getFunction) {
console.error(`Browser module load requires getFunction parameter for "${moduleNameOrUrl}"`);
return undefined;
}
const moduleResult = await this.getSafeWebModule(moduleNameOrUrl, getFunction);
return moduleResult as T;
}
return undefined;
}
2019-06-17 08:46:28 +02:00
2019-08-22 00:20:10 +02:00
public get isCI(): boolean {
if (this.isNode) {
if (process.env.CI) {
2019-06-17 08:46:28 +02:00
return true;
} else {
2019-06-17 08:46:28 +02:00
return false;
}
} else {
2019-06-17 08:46:28 +02:00
return false;
}
}
2019-08-22 00:20:10 +02:00
public async isMacAsync(): Promise<boolean> {
2019-06-17 08:46:28 +02:00
if (this.isNode) {
2022-03-16 16:09:24 +01:00
const os = await this.getSafeNodeModule('os');
2019-06-17 08:46:28 +02:00
return os.platform() === 'darwin';
} else {
2019-06-17 08:46:28 +02:00
return false;
}
}
2019-06-17 08:46:28 +02:00
2019-08-22 00:20:10 +02:00
public async isWindowsAsync(): Promise<boolean> {
2019-06-17 08:46:28 +02:00
if (this.isNode) {
2022-03-16 16:09:24 +01:00
const os = await this.getSafeNodeModule('os');
2019-06-17 08:46:28 +02:00
return os.platform() === 'win32';
} else {
2019-06-17 08:46:28 +02:00
return false;
}
}
2019-08-22 00:20:10 +02:00
public async isLinuxAsync(): Promise<boolean> {
2019-06-17 08:46:28 +02:00
if (this.isNode) {
2022-03-16 16:09:24 +01:00
const os = await this.getSafeNodeModule('os');
2019-06-17 08:46:28 +02:00
return os.platform() === 'linux';
} else {
2019-06-17 08:46:28 +02:00
return false;
}
}
2017-05-17 15:59:10 +02:00
/**
* prints the environment to console
*/
2019-08-22 00:20:10 +02:00
public async printEnv() {
2017-05-17 15:59:10 +02:00
if (this.isNode) {
2019-06-17 08:46:28 +02:00
console.log('running on NODE');
2022-12-28 19:43:48 +01:00
console.log('node version is ' + this.nodeVersion);
} else if (this.isDeno) {
console.log('running on DENO');
console.log('deno version is ' + this.denoVersion);
} else if (this.isBun) {
console.log('running on BUN');
console.log('bun version is ' + this.bunVersion);
2017-05-17 15:59:10 +02:00
} else {
2019-06-17 08:46:28 +02:00
console.log('running on BROWSER');
console.log('browser is ' + this.userAgent);
2017-05-17 15:59:10 +02:00
}
}
}