From d8fbf4c3e376a90e66a00b806b575945aa887158 Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Wed, 24 Jun 2026 14:05:10 +0200 Subject: [PATCH 1/3] feat: pull MDM config and check if managed [WPB-26373] --- README.md | 53 +++++++++ electron/src/lib/eventType.ts | 3 + electron/src/main.ts | 7 ++ .../src/managed/ManagedConfig.test.main.ts | 71 +++++++++++ electron/src/managed/ManagedConfig.ts | 72 ++++++++++++ electron/src/managed/backends/linux.ts | 41 +++++++ electron/src/managed/backends/macos.ts | 38 ++++++ electron/src/managed/backends/windows.ts | 111 ++++++++++++++++++ electron/src/managed/constants.ts | 46 ++++++++ electron/src/preload/preload-webview.ts | 14 ++- electron/src/types/globals.d.ts | 2 + package.json | 1 + 12 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 electron/src/managed/ManagedConfig.test.main.ts create mode 100644 electron/src/managed/ManagedConfig.ts create mode 100644 electron/src/managed/backends/linux.ts create mode 100644 electron/src/managed/backends/macos.ts create mode 100644 electron/src/managed/backends/windows.ts create mode 100644 electron/src/managed/constants.ts diff --git a/README.md b/README.md index 770908af8b5..0ae0ca2eb2f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,59 @@ yarn start yarn test ``` +### Managed device configuration (MDM) + +On company-managed devices, an MDM administrator can flag the device as managed so the webapp can enforce +App-lock regardless of the team-level setting. The desktop app reads a single `isManaged` flag at startup — +without spawning any shell command — and forwards it to the webapp as `window.desktopAppConfig.managedConfig`. + +The flag is read from organization-agnostic, Wire-vendor locations (no customer/company name is hardcoded): + +| OS | Source | Signal | +| :-- | :-- | :-- | +| Windows | Registry `…\SOFTWARE\Policies\Wire` (HKLM, HKCU fallback) | `isManaged` value = `1`, **or** the device is MDM-enrolled / Azure-AD joined | +| macOS | App's managed preferences domain (MDM AppConfig profile) | `isManaged` boolean = `true` | +| Linux | `/etc/wire/managed.json` | `{"isManaged": true}` (presence means managed unless explicitly `false`) | + +#### Testing locally + +For each OS: plant the flag, relaunch the app, then open DevTools on the webapp's `` and evaluate +`window.desktopAppConfig.managedConfig` — expect `{isManaged: true}`. With nothing planted it must be +`{isManaged: false}` and the app behaves as before. + +**Windows** (elevated prompt): + +```cmd +reg add "HKLM\SOFTWARE\Policies\Wire" /v isManaged /t REG_DWORD /d 1 /f +:: relaunch app -> isManaged === true +reg delete "HKLM\SOFTWARE\Policies\Wire" /f +``` + +Enrollment is detected automatically on an Intune/MDM-enrolled or Azure-AD-joined machine (no `Policies\Wire` +value needed). + +**macOS** (use the running build's bundle id — production is `com.wearezeta.zclient.mac`; dev/internal differ): + +```shell +defaults write com.wearezeta.zclient.mac isManaged -bool true +# relaunch app -> isManaged === true +defaults delete com.wearezeta.zclient.mac isManaged +``` + +To validate the real MDM path (managed-preferences domain): + +```shell +sudo defaults write "/Library/Managed Preferences/com.wearezeta.zclient.mac.plist" isManaged -bool true +``` + +**Linux**: + +```shell +echo '{"isManaged": true}' | sudo tee /etc/wire/managed.json +# relaunch app -> isManaged === true +sudo rm /etc/wire/managed.json +``` + ### Deployment | Stage | Branch | Action | Version | diff --git a/electron/src/lib/eventType.ts b/electron/src/lib/eventType.ts index bb5300bd1e7..980282c3b4f 100644 --- a/electron/src/lib/eventType.ts +++ b/electron/src/lib/eventType.ts @@ -75,6 +75,9 @@ export const EVENT_TYPE = { SIGN_OUT: 'EVENT_TYPE.LIFECYCLE.SIGN_OUT', UNREAD_COUNT: 'EVENT_TYPE.LIFECYCLE.UNREAD_COUNT', }, + MANAGED: { + GET_CONFIG: 'EVENT_TYPE.MANAGED.GET_CONFIG', + }, PREFERENCES: { SHOW: 'EVENT_TYPE.PREFERENCES.SHOW', }, diff --git a/electron/src/main.ts b/electron/src/main.ts index 46ee434e7e7..7b6665aa158 100644 --- a/electron/src/main.ts +++ b/electron/src/main.ts @@ -59,6 +59,7 @@ import {showErrorDialog} from './lib/showDialog'; import * as locale from './locale'; import {ENABLE_LOGGING, getLogger} from './logging/getLogger'; import {getLogFilenames} from './logging/loggerUtils'; +import {getManagedConfig} from './managed/ManagedConfig'; import {developerMenu, openDevTools} from './menu/developer'; import * as systemMenu from './menu/system'; import {TrayHandler} from './menu/TrayHandler'; @@ -198,6 +199,12 @@ const bindIpcEvents = (): void => { ipcMain.on(EVENT_TYPE.WRAPPER.RELAUNCH, () => lifecycle.relaunch()); ipcMain.on(EVENT_TYPE.ABOUT.SHOW, () => AboutWindow.showWindow()); + // Answered synchronously: the webview preload reads this via `ipcRenderer.sendSync` while it builds + // `window.desktopAppConfig`. The value is pre-read and memoized, so the handler does no I/O here. + ipcMain.on(EVENT_TYPE.MANAGED.GET_CONFIG, event => { + event.returnValue = getManagedConfig(); + }); + ipcMain.handle(EVENT_TYPE.ACTION.GET_OG_DATA, (_event, url) => getOpenGraphDataAsync(url)); ipcMain.on(EVENT_TYPE.ACTION.CHANGE_DOWNLOAD_LOCATION, (_event, downloadPath?: string) => { diff --git a/electron/src/managed/ManagedConfig.test.main.ts b/electron/src/managed/ManagedConfig.test.main.ts new file mode 100644 index 00000000000..0146171a04a --- /dev/null +++ b/electron/src/managed/ManagedConfig.test.main.ts @@ -0,0 +1,71 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import * as fs from 'fs-extra'; +import {fake, replace, restore} from 'sinon'; + +import * as assert from 'assert'; + +import {clearManagedConfigCache, getManagedConfig} from './ManagedConfig'; + +import * as EnvironmentUtil from '../runtime/EnvironmentUtil'; + +describe('getManagedConfig', () => { + beforeEach(() => { + clearManagedConfigCache(); + // Force the Linux code path so the test is deterministic regardless of the host OS. + replace(EnvironmentUtil.platform, 'IS_WINDOWS', false); + replace(EnvironmentUtil.platform, 'IS_MAC_OS', false); + replace(EnvironmentUtil.platform, 'IS_LINUX', true); + }); + + afterEach(() => { + restore(); + clearManagedConfigCache(); + }); + + it('reports managed when a managed config payload is present', () => { + replace(fs, 'readJSONSync', fake.returns({isManaged: true}) as any); + assert.deepStrictEqual(getManagedConfig(), {isManaged: true}); + }); + + it('reports unmanaged when no managed config is present', () => { + replace(fs, 'readJSONSync', fake.throws(new Error('ENOENT')) as any); + replace(fs, 'pathExistsSync', fake.returns(false) as any); + assert.deepStrictEqual(getManagedConfig(), {isManaged: false}); + }); + + it('treats a present payload as managed unless it explicitly opts out', () => { + replace(fs, 'readJSONSync', fake.returns({isManaged: false}) as any); + assert.deepStrictEqual(getManagedConfig(), {isManaged: false}); + }); + + it('memoizes the result and reads the underlying source only once', () => { + const readSource = fake.returns({isManaged: true}); + replace(fs, 'readJSONSync', readSource as any); + getManagedConfig(); + getManagedConfig(); + assert.strictEqual(readSource.callCount, 1); + }); + + it('never throws and defaults to unmanaged on an unrecognized platform', () => { + replace(EnvironmentUtil.platform, 'IS_LINUX', false); + assert.deepStrictEqual(getManagedConfig(), {isManaged: false}); + }); +}); diff --git a/electron/src/managed/ManagedConfig.ts b/electron/src/managed/ManagedConfig.ts new file mode 100644 index 00000000000..99f2970f275 --- /dev/null +++ b/electron/src/managed/ManagedConfig.ts @@ -0,0 +1,72 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {isDeviceManagedLinux} from './backends/linux'; +import {isDeviceManagedMacOS} from './backends/macos'; +import {isDeviceManagedWindows} from './backends/windows'; + +import {getLogger} from '../logging/getLogger'; +import * as EnvironmentUtil from '../runtime/EnvironmentUtil'; + +const logger = getLogger('ManagedConfig'); + +/** + * The single, OS-independent signal the desktop forwards to the webapp. The webapp owns App-lock + * enforcement; the desktop only reports whether the device is company-managed. + */ +export interface ManagedConfig { + isManaged: boolean; +} + +let cached: ManagedConfig | undefined; + +function detectIsManaged(): boolean { + if (EnvironmentUtil.platform.IS_WINDOWS) { + return isDeviceManagedWindows(); + } + if (EnvironmentUtil.platform.IS_MAC_OS) { + return isDeviceManagedMacOS(); + } + if (EnvironmentUtil.platform.IS_LINUX) { + return isDeviceManagedLinux(); + } + return false; +} + +// Reads the managed-device status once, then memoizes it: MDM config is static for an app session +// (a relaunch is required to pick up a re-push). Never throws — any failure is treated as unmanaged +// so non-managed devices and existing on-prem deployments are unaffected. +export function getManagedConfig(): ManagedConfig { + if (!cached) { + let isManaged = false; + try { + isManaged = detectIsManaged(); + } catch (error) { + logger.warn('Failed to read managed device configuration, treating the device as unmanaged:', error); + } + cached = {isManaged}; + logger.info(`Managed device detection: isManaged=${isManaged}`); + } + return cached; +} + +// Clears the memoized result. Primarily for tests; managed status is otherwise stable per session. +export function clearManagedConfigCache(): void { + cached = undefined; +} diff --git a/electron/src/managed/backends/linux.ts b/electron/src/managed/backends/linux.ts new file mode 100644 index 00000000000..d327bd5f91b --- /dev/null +++ b/electron/src/managed/backends/linux.ts @@ -0,0 +1,41 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import * as fs from 'fs-extra'; + +import {getLogger} from '../../logging/getLogger'; +import {LINUX_MANAGED_CONFIG_PATH, MANAGED_VALUE_NAME} from '../constants'; + +const logger = getLogger('ManagedConfig/linux'); + +// Linux has no standard MDM enrollment API, so managed status is driven by the presence of a +// machine-wide managed config file placed by the organization. The file's presence means managed, +// unless it explicitly opts out with `{"isManaged": false}`. A missing/unreadable file means unmanaged. +export function isDeviceManagedLinux(): boolean { + try { + const managedConfig = fs.readJSONSync(LINUX_MANAGED_CONFIG_PATH); + return managedConfig?.[MANAGED_VALUE_NAME] !== false; + } catch { + // Missing file is the normal unmanaged path; only log genuine parse errors. + if (fs.pathExistsSync(LINUX_MANAGED_CONFIG_PATH)) { + logger.warn(`Failed to parse ${LINUX_MANAGED_CONFIG_PATH}, treating the device as unmanaged.`); + } + return false; + } +} diff --git a/electron/src/managed/backends/macos.ts b/electron/src/managed/backends/macos.ts new file mode 100644 index 00000000000..91e9818e2db --- /dev/null +++ b/electron/src/managed/backends/macos.ts @@ -0,0 +1,38 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {systemPreferences} from 'electron'; + +import {getLogger} from '../../logging/getLogger'; +import {MANAGED_VALUE_NAME} from '../constants'; + +const logger = getLogger('ManagedConfig/macos'); + +// macOS does not expose MDM enrollment status without a shell tool (`profiles`), which is disallowed, +// and Electron has no enrollment API. So managed status is driven by the MDM-pushed managed app +// preference — the standard MDM AppConfig mechanism. `getUserDefault` reads it natively from the app's +// own defaults domain (no shell), returning `false` when the preference is not set. +export function isDeviceManagedMacOS(): boolean { + try { + return systemPreferences.getUserDefault(MANAGED_VALUE_NAME, 'boolean') === true; + } catch (error) { + logger.warn('Failed to read managed preference, treating the device as unmanaged:', error); + return false; + } +} diff --git a/electron/src/managed/backends/windows.ts b/electron/src/managed/backends/windows.ts new file mode 100644 index 00000000000..292c2bd86a8 --- /dev/null +++ b/electron/src/managed/backends/windows.ts @@ -0,0 +1,111 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {getLogger} from '../../logging/getLogger'; +import { + MANAGED_VALUE_NAME, + WINDOWS_CLOUD_DOMAIN_JOIN_KEY, + WINDOWS_ENROLLMENTS_KEY, + WINDOWS_POLICY_KEY, +} from '../constants'; + +const logger = getLogger('ManagedConfig/windows'); + +/** + * Minimal local typing for `registry-js`. The dependency is a Windows-only native module, so it is + * required lazily (below) rather than imported at the top level: this keeps it out of type-checking + * and out of the macOS/Linux runtime, where it is neither installed nor loadable. + */ +interface RegistryValue { + name: string; + type: string; + data: unknown; +} +interface RegistryJs { + HKEY: {HKEY_CURRENT_USER: number; HKEY_LOCAL_MACHINE: number}; + enumerateKeys: (hive: number, subkey: string) => string[]; + enumerateValues: (hive: number, subkey: string) => RegistryValue[]; +} + +function loadRegistry(): RegistryJs | undefined { + try { + // Native, Windows-only. Reads the registry via the Win32 API directly — no process spawn / no shell. + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('registry-js'); + } catch (error) { + logger.warn('registry-js is unavailable, treating the device as unmanaged:', error); + return undefined; + } +} + +function valuesOf(registry: RegistryJs, hive: number, subkey: string): RegistryValue[] { + try { + return registry.enumerateValues(hive, subkey) ?? []; + } catch { + return []; + } +} + +function subkeysOf(registry: RegistryJs, hive: number, subkey: string): string[] { + try { + return registry.enumerateKeys(hive, subkey) ?? []; + } catch { + return []; + } +} + +// Treats `1` (REG_DWORD) and `"1"`/`"true"` (REG_SZ) as the enabled managed flag. +function isManagedFlagEnabled(value: RegistryValue): boolean { + if (value.name !== MANAGED_VALUE_NAME) { + return false; + } + const {data} = value; + return data === 1 || data === true || data === '1' || (typeof data === 'string' && data.toLowerCase() === 'true'); +} + +// True when an organization has set the managed flag via Group Policy (machine-wide or per-user). +function hasWirePolicyPayload(registry: RegistryJs): boolean { + const {HKEY} = registry; + return [HKEY.HKEY_LOCAL_MACHINE, HKEY.HKEY_CURRENT_USER].some(hive => + valuesOf(registry, hive, WINDOWS_POLICY_KEY).some(isManagedFlagEnabled), + ); +} + +// True when the device is MDM-enrolled or Azure AD / Entra joined. +function isDeviceEnrolled(registry: RegistryJs): boolean { + const {HKEY} = registry; + + const isMdmEnrolled = subkeysOf(registry, HKEY.HKEY_LOCAL_MACHINE, WINDOWS_ENROLLMENTS_KEY).some(enrollmentKey => { + const values = valuesOf(registry, HKEY.HKEY_LOCAL_MACHINE, `${WINDOWS_ENROLLMENTS_KEY}\\${enrollmentKey}`); + return values.some(({name}) => name === 'UPN' || name === 'ProviderID'); + }); + if (isMdmEnrolled) { + return true; + } + + return subkeysOf(registry, HKEY.HKEY_LOCAL_MACHINE, WINDOWS_CLOUD_DOMAIN_JOIN_KEY).length > 0; +} + +export function isDeviceManagedWindows(): boolean { + const registry = loadRegistry(); + if (!registry) { + return false; + } + return hasWirePolicyPayload(registry) || isDeviceEnrolled(registry); +} diff --git a/electron/src/managed/constants.ts b/electron/src/managed/constants.ts new file mode 100644 index 00000000000..b09db74b498 --- /dev/null +++ b/electron/src/managed/constants.ts @@ -0,0 +1,46 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +/** + * Locations the desktop app reads to decide whether the device is company-managed. + * + * These are organization-agnostic, Wire-vendor locations: no specific customer/company + * name is encoded. Any organization's MDM writes the managed payload into these standard + * Wire locations, so the feature ships once and works for every organization. + * + * NOTE: the exact key/value names below should be confirmed with the MDM and mobile/webapp + * teams so all platforms read the same MDM payload convention. + */ + +/** The managed flag an organization sets to `true`/`1`. Same name across all platforms for consistency. */ +export const MANAGED_VALUE_NAME = 'isManaged'; + +/** Windows: machine-wide Group Policy key. The `MANAGED_VALUE_NAME` value under it drives managed status. */ +export const WINDOWS_POLICY_KEY = 'SOFTWARE\\Policies\\Wire'; + +/** Windows: MDM enrollment registry. A subkey carrying a `UPN`/`ProviderID` means the device is enrolled. */ +export const WINDOWS_ENROLLMENTS_KEY = 'SOFTWARE\\Microsoft\\Enrollments'; + +/** Windows: Azure AD / Entra device join. A subkey under JoinInfo means the device is joined. */ +export const WINDOWS_CLOUD_DOMAIN_JOIN_KEY = 'SYSTEM\\CurrentControlSet\\Control\\CloudDomainJoin\\JoinInfo'; + +// macOS reads `MANAGED_VALUE_NAME` from the app's defaults domain (pushed via an MDM AppConfig profile). + +/** Linux: machine-wide managed config file holding the `MANAGED_VALUE_NAME` flag. */ +export const LINUX_MANAGED_CONFIG_PATH = '/etc/wire/managed.json'; diff --git a/electron/src/preload/preload-webview.ts b/electron/src/preload/preload-webview.ts index a73d19c2457..3d4dfac6f8a 100644 --- a/electron/src/preload/preload-webview.ts +++ b/electron/src/preload/preload-webview.ts @@ -281,7 +281,19 @@ process.once('loaded', () => { version: 1, }; global.environment = EnvironmentUtil; - global.desktopAppConfig = {version: EnvironmentUtil.app.DESKTOP_VERSION, supportsCallingPopoutWindow: true}; + // Read synchronously so the value is present at the exact point `desktopAppConfig` is assigned. + // The main-process handler returns a pre-read, memoized value, so the blocking call is negligible. + let managedConfig = {isManaged: false}; + try { + managedConfig = ipcRenderer.sendSync(EVENT_TYPE.MANAGED.GET_CONFIG) ?? {isManaged: false}; + } catch (error) { + logger.warn('Failed to read managed config from the main process, treating the device as unmanaged:', error); + } + global.desktopAppConfig = { + version: EnvironmentUtil.app.DESKTOP_VERSION, + supportsCallingPopoutWindow: true, + managedConfig, + }; global.openGraphAsync = getOpenGraphDataViaChannel; global.setImmediate = _setImmediate; }); diff --git a/electron/src/types/globals.d.ts b/electron/src/types/globals.d.ts index a554976b502..c6216f5f223 100644 --- a/electron/src/types/globals.d.ts +++ b/electron/src/types/globals.d.ts @@ -23,6 +23,7 @@ import type {Data as OpenGraphResult} from 'open-graph'; import type {WebAppEvents} from '@wireapp/webapp-events'; import type {i18nStrings, SupportedI18nLanguage} from '../locale'; +import type {ManagedConfig} from '../managed/ManagedConfig'; import type * as EnvironmentUtil from '../runtime/EnvironmentUtil'; export declare global { @@ -46,6 +47,7 @@ export declare global { var desktopAppConfig: { version: string; supportsCallingPopoutWindow?: boolean; + managedConfig?: ManagedConfig; }; /* eslint-enable no-var */ diff --git a/package.json b/package.json index b2e68a3aa19..410e15f8fff 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "redux": "4.2.1", "redux-logger": "3.0.6", "redux-thunk": "2.4.2", + "registry-js": "1.16.1", "uuid": "9.0.1" }, "description": "The most secure collaboration platform.", From 0d363749104a6a95147152913b0b91b16e991e55 Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Mon, 29 Jun 2026 12:37:47 +0200 Subject: [PATCH 2/3] fix: update the prop name --- README.md | 32 +++++++++---------- .../src/managed/ManagedConfig.test.main.ts | 22 ++++++------- electron/src/managed/ManagedConfig.ts | 15 +++++---- electron/src/managed/backends/linux.ts | 6 ++-- electron/src/managed/backends/macos.ts | 4 +-- electron/src/managed/backends/windows.ts | 12 +++---- electron/src/managed/constants.ts | 12 +++---- electron/src/preload/preload-webview.ts | 4 +-- 8 files changed, 54 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 0ae0ca2eb2f..36703df359d 100644 --- a/README.md +++ b/README.md @@ -56,29 +56,29 @@ yarn test ### Managed device configuration (MDM) -On company-managed devices, an MDM administrator can flag the device as managed so the webapp can enforce -App-lock regardless of the team-level setting. The desktop app reads a single `isManaged` flag at startup — -without spawning any shell command — and forwards it to the webapp as `window.desktopAppConfig.managedConfig`. +On company-managed devices, an MDM administrator can force App-lock on, overriding the team-level App-lock +setting. The desktop app reads a single `applockOverride` flag at startup — without spawning any shell +command — and forwards it to the webapp as `window.desktopAppConfig.managedConfig`. The flag is read from organization-agnostic, Wire-vendor locations (no customer/company name is hardcoded): | OS | Source | Signal | | :-- | :-- | :-- | -| Windows | Registry `…\SOFTWARE\Policies\Wire` (HKLM, HKCU fallback) | `isManaged` value = `1`, **or** the device is MDM-enrolled / Azure-AD joined | -| macOS | App's managed preferences domain (MDM AppConfig profile) | `isManaged` boolean = `true` | -| Linux | `/etc/wire/managed.json` | `{"isManaged": true}` (presence means managed unless explicitly `false`) | +| Windows | Registry `…\SOFTWARE\Policies\Wire` (HKLM, HKCU fallback) | `applockOverride` value = `1`, **or** the device is MDM-enrolled / Azure-AD joined | +| macOS | App's managed preferences domain (MDM AppConfig profile) | `applockOverride` boolean = `true` | +| Linux | `/etc/wire/managed.json` | `{"applockOverride": true}` (presence means managed unless explicitly `false`) | #### Testing locally For each OS: plant the flag, relaunch the app, then open DevTools on the webapp's `` and evaluate -`window.desktopAppConfig.managedConfig` — expect `{isManaged: true}`. With nothing planted it must be -`{isManaged: false}` and the app behaves as before. +`window.desktopAppConfig.managedConfig` — expect `{applockOverride: true}`. With nothing planted it must be +`{applockOverride: false}` and the app behaves as before. **Windows** (elevated prompt): ```cmd -reg add "HKLM\SOFTWARE\Policies\Wire" /v isManaged /t REG_DWORD /d 1 /f -:: relaunch app -> isManaged === true +reg add "HKLM\SOFTWARE\Policies\Wire" /v applockOverride /t REG_DWORD /d 1 /f +:: relaunch app -> applockOverride === true reg delete "HKLM\SOFTWARE\Policies\Wire" /f ``` @@ -88,22 +88,22 @@ value needed). **macOS** (use the running build's bundle id — production is `com.wearezeta.zclient.mac`; dev/internal differ): ```shell -defaults write com.wearezeta.zclient.mac isManaged -bool true -# relaunch app -> isManaged === true -defaults delete com.wearezeta.zclient.mac isManaged +defaults write com.wearezeta.zclient.mac applockOverride -bool true +# relaunch app -> applockOverride === true +defaults delete com.wearezeta.zclient.mac applockOverride ``` To validate the real MDM path (managed-preferences domain): ```shell -sudo defaults write "/Library/Managed Preferences/com.wearezeta.zclient.mac.plist" isManaged -bool true +sudo defaults write "/Library/Managed Preferences/com.wearezeta.zclient.mac.plist" applockOverride -bool true ``` **Linux**: ```shell -echo '{"isManaged": true}' | sudo tee /etc/wire/managed.json -# relaunch app -> isManaged === true +echo '{"applockOverride": true}' | sudo tee /etc/wire/managed.json +# relaunch app -> applockOverride === true sudo rm /etc/wire/managed.json ``` diff --git a/electron/src/managed/ManagedConfig.test.main.ts b/electron/src/managed/ManagedConfig.test.main.ts index 0146171a04a..662795ffc26 100644 --- a/electron/src/managed/ManagedConfig.test.main.ts +++ b/electron/src/managed/ManagedConfig.test.main.ts @@ -40,32 +40,32 @@ describe('getManagedConfig', () => { clearManagedConfigCache(); }); - it('reports managed when a managed config payload is present', () => { - replace(fs, 'readJSONSync', fake.returns({isManaged: true}) as any); - assert.deepStrictEqual(getManagedConfig(), {isManaged: true}); + it('enables the override when a managed config payload is present', () => { + replace(fs, 'readJSONSync', fake.returns({applockOverride: true}) as any); + assert.deepStrictEqual(getManagedConfig(), {applockOverride: true}); }); - it('reports unmanaged when no managed config is present', () => { + it('disables the override when no managed config is present', () => { replace(fs, 'readJSONSync', fake.throws(new Error('ENOENT')) as any); replace(fs, 'pathExistsSync', fake.returns(false) as any); - assert.deepStrictEqual(getManagedConfig(), {isManaged: false}); + assert.deepStrictEqual(getManagedConfig(), {applockOverride: false}); }); - it('treats a present payload as managed unless it explicitly opts out', () => { - replace(fs, 'readJSONSync', fake.returns({isManaged: false}) as any); - assert.deepStrictEqual(getManagedConfig(), {isManaged: false}); + it('treats a present payload as enabled unless it explicitly opts out', () => { + replace(fs, 'readJSONSync', fake.returns({applockOverride: false}) as any); + assert.deepStrictEqual(getManagedConfig(), {applockOverride: false}); }); it('memoizes the result and reads the underlying source only once', () => { - const readSource = fake.returns({isManaged: true}); + const readSource = fake.returns({applockOverride: true}); replace(fs, 'readJSONSync', readSource as any); getManagedConfig(); getManagedConfig(); assert.strictEqual(readSource.callCount, 1); }); - it('never throws and defaults to unmanaged on an unrecognized platform', () => { + it('never throws and defaults to disabled on an unrecognized platform', () => { replace(EnvironmentUtil.platform, 'IS_LINUX', false); - assert.deepStrictEqual(getManagedConfig(), {isManaged: false}); + assert.deepStrictEqual(getManagedConfig(), {applockOverride: false}); }); }); diff --git a/electron/src/managed/ManagedConfig.ts b/electron/src/managed/ManagedConfig.ts index 99f2970f275..47cf2f2cfcd 100644 --- a/electron/src/managed/ManagedConfig.ts +++ b/electron/src/managed/ManagedConfig.ts @@ -28,15 +28,16 @@ const logger = getLogger('ManagedConfig'); /** * The single, OS-independent signal the desktop forwards to the webapp. The webapp owns App-lock - * enforcement; the desktop only reports whether the device is company-managed. + * enforcement; the desktop only reports whether the App-lock override applies. */ export interface ManagedConfig { - isManaged: boolean; + /** When true, App-lock is forced on, overriding the team-level App-lock setting. */ + applockOverride: boolean; } let cached: ManagedConfig | undefined; -function detectIsManaged(): boolean { +function detectApplockOverride(): boolean { if (EnvironmentUtil.platform.IS_WINDOWS) { return isDeviceManagedWindows(); } @@ -54,14 +55,14 @@ function detectIsManaged(): boolean { // so non-managed devices and existing on-prem deployments are unaffected. export function getManagedConfig(): ManagedConfig { if (!cached) { - let isManaged = false; + let applockOverride = false; try { - isManaged = detectIsManaged(); + applockOverride = detectApplockOverride(); } catch (error) { logger.warn('Failed to read managed device configuration, treating the device as unmanaged:', error); } - cached = {isManaged}; - logger.info(`Managed device detection: isManaged=${isManaged}`); + cached = {applockOverride}; + logger.info(`Managed device detection: applockOverride=${applockOverride}`); } return cached; } diff --git a/electron/src/managed/backends/linux.ts b/electron/src/managed/backends/linux.ts index d327bd5f91b..1fb84bc13b2 100644 --- a/electron/src/managed/backends/linux.ts +++ b/electron/src/managed/backends/linux.ts @@ -20,17 +20,17 @@ import * as fs from 'fs-extra'; import {getLogger} from '../../logging/getLogger'; -import {LINUX_MANAGED_CONFIG_PATH, MANAGED_VALUE_NAME} from '../constants'; +import {APPLOCK_OVERRIDE_KEY, LINUX_MANAGED_CONFIG_PATH} from '../constants'; const logger = getLogger('ManagedConfig/linux'); // Linux has no standard MDM enrollment API, so managed status is driven by the presence of a // machine-wide managed config file placed by the organization. The file's presence means managed, -// unless it explicitly opts out with `{"isManaged": false}`. A missing/unreadable file means unmanaged. +// unless it explicitly opts out with `{"applockOverride": false}`. A missing/unreadable file means unmanaged. export function isDeviceManagedLinux(): boolean { try { const managedConfig = fs.readJSONSync(LINUX_MANAGED_CONFIG_PATH); - return managedConfig?.[MANAGED_VALUE_NAME] !== false; + return managedConfig?.[APPLOCK_OVERRIDE_KEY] !== false; } catch { // Missing file is the normal unmanaged path; only log genuine parse errors. if (fs.pathExistsSync(LINUX_MANAGED_CONFIG_PATH)) { diff --git a/electron/src/managed/backends/macos.ts b/electron/src/managed/backends/macos.ts index 91e9818e2db..c63b06a910e 100644 --- a/electron/src/managed/backends/macos.ts +++ b/electron/src/managed/backends/macos.ts @@ -20,7 +20,7 @@ import {systemPreferences} from 'electron'; import {getLogger} from '../../logging/getLogger'; -import {MANAGED_VALUE_NAME} from '../constants'; +import {APPLOCK_OVERRIDE_KEY} from '../constants'; const logger = getLogger('ManagedConfig/macos'); @@ -30,7 +30,7 @@ const logger = getLogger('ManagedConfig/macos'); // own defaults domain (no shell), returning `false` when the preference is not set. export function isDeviceManagedMacOS(): boolean { try { - return systemPreferences.getUserDefault(MANAGED_VALUE_NAME, 'boolean') === true; + return systemPreferences.getUserDefault(APPLOCK_OVERRIDE_KEY, 'boolean') === true; } catch (error) { logger.warn('Failed to read managed preference, treating the device as unmanaged:', error); return false; diff --git a/electron/src/managed/backends/windows.ts b/electron/src/managed/backends/windows.ts index 292c2bd86a8..bbfa33f9a23 100644 --- a/electron/src/managed/backends/windows.ts +++ b/electron/src/managed/backends/windows.ts @@ -19,7 +19,7 @@ import {getLogger} from '../../logging/getLogger'; import { - MANAGED_VALUE_NAME, + APPLOCK_OVERRIDE_KEY, WINDOWS_CLOUD_DOMAIN_JOIN_KEY, WINDOWS_ENROLLMENTS_KEY, WINDOWS_POLICY_KEY, @@ -70,20 +70,20 @@ function subkeysOf(registry: RegistryJs, hive: number, subkey: string): string[] } } -// Treats `1` (REG_DWORD) and `"1"`/`"true"` (REG_SZ) as the enabled managed flag. -function isManagedFlagEnabled(value: RegistryValue): boolean { - if (value.name !== MANAGED_VALUE_NAME) { +// Treats `1` (REG_DWORD) and `"1"`/`"true"` (REG_SZ) as the enabled override flag. +function isOverrideEnabled(value: RegistryValue): boolean { + if (value.name !== APPLOCK_OVERRIDE_KEY) { return false; } const {data} = value; return data === 1 || data === true || data === '1' || (typeof data === 'string' && data.toLowerCase() === 'true'); } -// True when an organization has set the managed flag via Group Policy (machine-wide or per-user). +// True when an organization has set the App-lock override flag via Group Policy (machine-wide or per-user). function hasWirePolicyPayload(registry: RegistryJs): boolean { const {HKEY} = registry; return [HKEY.HKEY_LOCAL_MACHINE, HKEY.HKEY_CURRENT_USER].some(hive => - valuesOf(registry, hive, WINDOWS_POLICY_KEY).some(isManagedFlagEnabled), + valuesOf(registry, hive, WINDOWS_POLICY_KEY).some(isOverrideEnabled), ); } diff --git a/electron/src/managed/constants.ts b/electron/src/managed/constants.ts index b09db74b498..681fd530c12 100644 --- a/electron/src/managed/constants.ts +++ b/electron/src/managed/constants.ts @@ -18,7 +18,7 @@ */ /** - * Locations the desktop app reads to decide whether the device is company-managed. + * Locations the desktop app reads to decide whether the App-lock override is enforced. * * These are organization-agnostic, Wire-vendor locations: no specific customer/company * name is encoded. Any organization's MDM writes the managed payload into these standard @@ -28,10 +28,10 @@ * teams so all platforms read the same MDM payload convention. */ -/** The managed flag an organization sets to `true`/`1`. Same name across all platforms for consistency. */ -export const MANAGED_VALUE_NAME = 'isManaged'; +/** The App-lock override flag an organization sets to `true`/`1`. Same name across all platforms. */ +export const APPLOCK_OVERRIDE_KEY = 'applockOverride'; -/** Windows: machine-wide Group Policy key. The `MANAGED_VALUE_NAME` value under it drives managed status. */ +/** Windows: machine-wide Group Policy key. The `APPLOCK_OVERRIDE_KEY` value under it drives the override. */ export const WINDOWS_POLICY_KEY = 'SOFTWARE\\Policies\\Wire'; /** Windows: MDM enrollment registry. A subkey carrying a `UPN`/`ProviderID` means the device is enrolled. */ @@ -40,7 +40,7 @@ export const WINDOWS_ENROLLMENTS_KEY = 'SOFTWARE\\Microsoft\\Enrollments'; /** Windows: Azure AD / Entra device join. A subkey under JoinInfo means the device is joined. */ export const WINDOWS_CLOUD_DOMAIN_JOIN_KEY = 'SYSTEM\\CurrentControlSet\\Control\\CloudDomainJoin\\JoinInfo'; -// macOS reads `MANAGED_VALUE_NAME` from the app's defaults domain (pushed via an MDM AppConfig profile). +// macOS reads `APPLOCK_OVERRIDE_KEY` from the app's defaults domain (pushed via an MDM AppConfig profile). -/** Linux: machine-wide managed config file holding the `MANAGED_VALUE_NAME` flag. */ +/** Linux: machine-wide managed config file holding the `APPLOCK_OVERRIDE_KEY` flag. */ export const LINUX_MANAGED_CONFIG_PATH = '/etc/wire/managed.json'; diff --git a/electron/src/preload/preload-webview.ts b/electron/src/preload/preload-webview.ts index 3d4dfac6f8a..1b3e634122e 100644 --- a/electron/src/preload/preload-webview.ts +++ b/electron/src/preload/preload-webview.ts @@ -283,9 +283,9 @@ process.once('loaded', () => { global.environment = EnvironmentUtil; // Read synchronously so the value is present at the exact point `desktopAppConfig` is assigned. // The main-process handler returns a pre-read, memoized value, so the blocking call is negligible. - let managedConfig = {isManaged: false}; + let managedConfig = {applockOverride: false}; try { - managedConfig = ipcRenderer.sendSync(EVENT_TYPE.MANAGED.GET_CONFIG) ?? {isManaged: false}; + managedConfig = ipcRenderer.sendSync(EVENT_TYPE.MANAGED.GET_CONFIG) ?? {applockOverride: false}; } catch (error) { logger.warn('Failed to read managed config from the main process, treating the device as unmanaged:', error); } From b5ff68a0abdfa4ef53099dec374025abff1f8c81 Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Mon, 29 Jun 2026 12:45:06 +0200 Subject: [PATCH 3/3] fix: add yarn lock --- yarn.lock | 355 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 333 insertions(+), 22 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1b7801b2bd1..b089b5bde7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6180,6 +6180,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^2.0.0": + version: 2.1.1 + resolution: "ansi-regex@npm:2.1.1" + checksum: 190abd03e4ff86794f338a31795d262c1dfe8c91f7e01d04f13f646f1dcb16c5800818f886047876f1272f065570ab86b24b99089f8b68a0e11ff19aed4ca8f1 + languageName: node + linkType: hard + "ansi-regex@npm:^6.0.1": version: 6.0.1 resolution: "ansi-regex@npm:6.0.1" @@ -6302,6 +6309,13 @@ __metadata: languageName: node linkType: hard +"aproba@npm:^1.0.3": + version: 1.2.0 + resolution: "aproba@npm:1.2.0" + checksum: 0fca141966559d195072ed047658b6e6c4fe92428c385dd38e288eacfc55807e7b4989322f030faff32c0f46bb0bc10f1e0ac32ec22d25315a1e5bbc0ebb76dc + languageName: node + linkType: hard + "aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -6333,6 +6347,16 @@ __metadata: languageName: node linkType: hard +"are-we-there-yet@npm:~1.1.2": + version: 1.1.7 + resolution: "are-we-there-yet@npm:1.1.7" + dependencies: + delegates: ^1.0.0 + readable-stream: ^2.0.6 + checksum: 70d251719c969b2745bfe5ddf3ebaefa846a636e90a6d5212573676af5d6670e15457761d4725731e19cbebdce42c4ab0cbedf23ab047f2a08274985aa10a3c7 + languageName: node + linkType: hard + "arg@npm:^4.1.0": version: 4.1.3 resolution: "arg@npm:4.1.3" @@ -7090,7 +7114,7 @@ __metadata: languageName: node linkType: hard -"bl@npm:^4.1.0": +"bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" dependencies: @@ -7612,6 +7636,13 @@ __metadata: languageName: node linkType: hard +"chownr@npm:^1.1.1": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: 115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -7754,6 +7785,13 @@ __metadata: languageName: node linkType: hard +"code-point-at@npm:^1.0.0": + version: 1.1.0 + resolution: "code-point-at@npm:1.1.0" + checksum: 17d5666611f9b16d64fdf48176d9b7fb1c7d1c1607a189f7e600040a11a6616982876af148230336adb7d8fe728a559f743a4e29db3747e3b1a32fa7f4529681 + languageName: node + linkType: hard + "collect-v8-coverage@npm:^1.0.0": version: 1.0.1 resolution: "collect-v8-coverage@npm:1.0.1" @@ -7921,7 +7959,7 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.1.0": +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0, console-control-strings@npm:~1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed @@ -8394,6 +8432,15 @@ __metadata: languageName: node linkType: hard +"decompress-response@npm:^4.2.0": + version: 4.2.1 + resolution: "decompress-response@npm:4.2.1" + dependencies: + mimic-response: ^2.0.0 + checksum: 4e783ca4dfe9417354d61349750fe05236f565a4415a6ca20983a311be2371debaedd9104c0b0e7b36e5f167aeaae04f84f1a0b3f8be4162f1d7d15598b8fdba + languageName: node + linkType: hard + "decompress-response@npm:^6.0.0": version: 6.0.0 resolution: "decompress-response@npm:6.0.0" @@ -8448,6 +8495,13 @@ __metadata: languageName: node linkType: hard +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 + languageName: node + linkType: hard + "deep-is@npm:^0.1.3, deep-is@npm:~0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -8596,6 +8650,15 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^1.0.3": + version: 1.0.3 + resolution: "detect-libc@npm:1.0.3" + bin: + detect-libc: ./bin/detect-libc.js + checksum: daaaed925ffa7889bd91d56e9624e6c8033911bb60f3a50a74a87500680652969dbaab9526d1e200a4c94acf80fc862a22131841145a0a8482d60a99c24f4a3e + languageName: node + linkType: hard + "detect-libc@npm:^2.0.1": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" @@ -9115,6 +9178,15 @@ __metadata: languageName: node linkType: hard +"end-of-stream@npm:^1.4.1": + version: 1.4.5 + resolution: "end-of-stream@npm:1.4.5" + dependencies: + once: ^1.4.0 + checksum: 1e0cfa6e7f49887544e03314f9dfc56a8cb6dde910cbb445983ecc2ff426fc05946df9d75d8a21a3a64f2cecfe1bf88f773952029f46756b2ed64a24e95b1fb8 + languageName: node + linkType: hard + "enhanced-resolve@npm:^5.15.0": version: 5.17.1 resolution: "enhanced-resolve@npm:5.17.1" @@ -10723,6 +10795,13 @@ __metadata: languageName: node linkType: hard +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 588c19847216421ed92befb521767b7018dc88f88b0576df98cb242f20961425e96a92cbece525ef28cc5becceae5d544ae0f5b9b5e2aa05acb13716ca5b3099 + languageName: node + linkType: hard + "expand-tilde@npm:^1.2.2": version: 1.2.2 resolution: "expand-tilde@npm:1.2.2" @@ -11306,6 +11385,13 @@ __metadata: languageName: node linkType: hard +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d + languageName: node + linkType: hard + "fs-exists-sync@npm:^0.1.0": version: 0.1.0 resolution: "fs-exists-sync@npm:0.1.0" @@ -11523,6 +11609,22 @@ __metadata: languageName: node linkType: hard +"gauge@npm:~2.7.3": + version: 2.7.4 + resolution: "gauge@npm:2.7.4" + dependencies: + aproba: ^1.0.3 + console-control-strings: ^1.0.0 + has-unicode: ^2.0.0 + object-assign: ^4.1.0 + signal-exit: ^3.0.0 + string-width: ^1.0.1 + strip-ansi: ^3.0.1 + wide-align: ^1.1.0 + checksum: a89b53cee65579b46832e050b5f3a79a832cc422c190de79c6b8e2e15296ab92faddde6ddf2d376875cbba2b043efa99b9e1ed8124e7365f61b04e3cee9d40ee + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -11729,6 +11831,13 @@ __metadata: languageName: node linkType: hard +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 14e448192a35c1e42efee94c9d01a10f42fe790375891a24b25261246ce9336ab9df5d274585aedd4568f7922246c2a78b8a8cd2571bfe99c693a9718e7dd0e3 + languageName: node + linkType: hard + "glob-parent@npm:^2.0.0": version: 2.0.0 resolution: "glob-parent@npm:2.0.0" @@ -12086,7 +12195,7 @@ __metadata: languageName: node linkType: hard -"has-unicode@npm:^2.0.1": +"has-unicode@npm:^2.0.0, has-unicode@npm:^2.0.1": version: 2.0.1 resolution: "has-unicode@npm:2.0.1" checksum: 1eab07a7436512db0be40a710b29b5dc21fa04880b7f63c9980b706683127e3c1b57cb80ea96d47991bdae2dfe479604f6a1ba410106ee1046a41d1bd0814400 @@ -12445,7 +12554,7 @@ __metadata: languageName: node linkType: hard -"ini@npm:^1.3.4": +"ini@npm:^1.3.4, ini@npm:~1.3.0": version: 1.3.8 resolution: "ini@npm:1.3.8" checksum: dfd98b0ca3a4fc1e323e38a6c8eb8936e31a97a918d3b377649ea15bdb15d481207a0dda1021efbd86b464cae29a0d33c1d7dcaf6c5672bee17fa849bc50a1b3 @@ -12823,6 +12932,15 @@ __metadata: languageName: node linkType: hard +"is-fullwidth-code-point@npm:^1.0.0": + version: 1.0.0 + resolution: "is-fullwidth-code-point@npm:1.0.0" + dependencies: + number-is-nan: ^1.0.0 + checksum: 4d46a7465a66a8aebcc5340d3b63a56602133874af576a9ca42c6f0f4bd787a743605771c5f246db77da96605fefeffb65fc1dbe862dcc7328f4b4d03edf5a57 + languageName: node + linkType: hard + "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -15056,6 +15174,13 @@ __metadata: languageName: node linkType: hard +"mimic-response@npm:^2.0.0": + version: 2.1.0 + resolution: "mimic-response@npm:2.1.0" + checksum: 014fad6ab936657e5f2f48bd87af62a8e928ebe84472aaf9e14fec4fcb31257a5edff77324d8ac13ddc6685ba5135cf16e381efac324e5f174fb4ddbf902bf07 + languageName: node + linkType: hard + "mimic-response@npm:^3.1.0": version: 3.1.0 resolution: "mimic-response@npm:3.1.0" @@ -15126,7 +15251,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:1.2.8, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8": +"minimist@npm:1.2.8, minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 @@ -15224,6 +15349,13 @@ __metadata: languageName: node linkType: hard +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac + languageName: node + linkType: hard + "mkdirp@npm:^0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" @@ -15343,6 +15475,13 @@ __metadata: languageName: node linkType: hard +"napi-build-utils@npm:^1.0.1": + version: 1.0.2 + resolution: "napi-build-utils@npm:1.0.2" + checksum: 06c14271ee966e108d55ae109f340976a9556c8603e888037145d6522726aebe89dd0c861b4b83947feaf6d39e79e08817559e8693deedc2c94e82c5cbd090c7 + languageName: node + linkType: hard + "napi-postinstall@npm:^0.3.0": version: 0.3.2 resolution: "napi-postinstall@npm:0.3.2" @@ -15397,6 +15536,15 @@ __metadata: languageName: node linkType: hard +"node-abi@npm:^2.7.0": + version: 2.30.1 + resolution: "node-abi@npm:2.30.1" + dependencies: + semver: ^5.4.1 + checksum: 3f4b0c912ce4befcd7ceab4493ba90b51d60dfcc90f567c93f731d897ef8691add601cb64c181683b800f21d479d68f9a6e15d8ab8acd16a5706333b9e30a881 + languageName: node + linkType: hard + "node-abi@npm:^3.45.0": version: 3.85.0 resolution: "node-abi@npm:3.85.0" @@ -15415,6 +15563,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^3.2.1": + version: 3.2.1 + resolution: "node-addon-api@npm:3.2.1" + dependencies: + node-gyp: latest + checksum: 2369986bb0881ccd9ef6bacdf39550e07e089a9c8ede1cbc5fc7712d8e2faa4d50da0e487e333d4125f8c7a616c730131d1091676c9d499af1d74560756b4a18 + languageName: node + linkType: hard + "node-api-version@npm:^0.2.0": version: 0.2.1 resolution: "node-api-version@npm:0.2.1" @@ -15502,6 +15659,13 @@ __metadata: languageName: node linkType: hard +"noop-logger@npm:^0.1.1": + version: 0.1.1 + resolution: "noop-logger@npm:0.1.1" + checksum: 9f99da270d074a2f268de2eae3ebcb44f12cc2f7241417c7be9f1e206f614afa632a27b91febab86163f88bb54466d638e49c9f62d899105f18d5ed5bcd51ed1 + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -15567,6 +15731,18 @@ __metadata: languageName: node linkType: hard +"npmlog@npm:^4.0.1": + version: 4.1.2 + resolution: "npmlog@npm:4.1.2" + dependencies: + are-we-there-yet: ~1.1.2 + console-control-strings: ~1.1.0 + gauge: ~2.7.3 + set-blocking: ~2.0.0 + checksum: edbda9f95ec20957a892de1839afc6fb735054c3accf6fbefe767bac9a639fd5cea2baeac6bd2bcd50a85cb54924d57d9886c81c7fbc2332c2ddd19227504192 + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -15588,6 +15764,13 @@ __metadata: languageName: node linkType: hard +"number-is-nan@npm:^1.0.0": + version: 1.0.1 + resolution: "number-is-nan@npm:1.0.1" + checksum: 13656bc9aa771b96cef209ffca31c31a03b507ca6862ba7c3f638a283560620d723d52e626d57892c7fff475f4c36ac07f0600f14544692ff595abff214b9ffb + languageName: node + linkType: hard + "nwsapi@npm:^2.2.2": version: 2.2.4 resolution: "nwsapi@npm:2.2.4" @@ -15657,7 +15840,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.1.1": +"object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -16547,6 +16730,31 @@ __metadata: languageName: node linkType: hard +"prebuild-install@npm:^5.3.5": + version: 5.3.6 + resolution: "prebuild-install@npm:5.3.6" + dependencies: + detect-libc: ^1.0.3 + expand-template: ^2.0.3 + github-from-package: 0.0.0 + minimist: ^1.2.3 + mkdirp-classic: ^0.5.3 + napi-build-utils: ^1.0.1 + node-abi: ^2.7.0 + noop-logger: ^0.1.1 + npmlog: ^4.0.1 + pump: ^3.0.0 + rc: ^1.2.7 + simple-get: ^3.0.3 + tar-fs: ^2.0.0 + tunnel-agent: ^0.6.0 + which-pm-runs: ^1.0.0 + bin: + prebuild-install: bin.js + checksum: 9b99e5ea2c1db44efbd1bc1f3d04f887e66ae282af8560191ee3005886c8d3fab578ad3e903d0965fec082d3c0779e6337a63152dc9d0f847f1bc95317356ea1 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -16826,6 +17034,20 @@ __metadata: languageName: node linkType: hard +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: ^0.6.0 + ini: ~1.3.0 + minimist: ^1.2.0 + strip-json-comments: ~2.0.1 + bin: + rc: ./cli.js + checksum: 2e26e052f8be2abd64e6d1dabfbd7be03f80ec18ccbc49562d31f617d0015fbdbcf0f9eed30346ea6ab789e0fdfe4337f033f8016efdbee0df5354751842080e + languageName: node + linkType: hard + "rcedit@npm:^3.0.1": version: 3.0.1 resolution: "rcedit@npm:3.0.1" @@ -17020,18 +17242,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" - dependencies: - inherits: ^2.0.3 - string_decoder: ^1.1.1 - util-deprecate: ^1.0.1 - checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d - languageName: node - linkType: hard - -"readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.6, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -17046,6 +17257,17 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -17264,6 +17486,17 @@ __metadata: languageName: node linkType: hard +"registry-js@npm:1.16.1": + version: 1.16.1 + resolution: "registry-js@npm:1.16.1" + dependencies: + node-addon-api: ^3.2.1 + node-gyp: latest + prebuild-install: ^5.3.5 + checksum: 4d349fc226970b73e8bca553f38d232008e0c0b63a6641cacd68f4709b2d8b79ec03d95449fd374219c93de4481c8218ffe334f37ba65a5d98a47032aea6176c + languageName: node + linkType: hard + "regjsgen@npm:^0.8.0": version: 0.8.0 resolution: "regjsgen@npm:0.8.0" @@ -17922,7 +18155,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.6.0": +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.4.1, semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" bin: @@ -18036,7 +18269,7 @@ __metadata: languageName: node linkType: hard -"set-blocking@npm:^2.0.0": +"set-blocking@npm:^2.0.0, set-blocking@npm:~2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" checksum: 6e65a05f7cf7ebdf8b7c75b101e18c0b7e3dff4940d480efed8aad3a36a4005140b660fa1d804cb8bce911cac290441dc728084a30504d3516ac2ff7ad607b02 @@ -18205,7 +18438,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -18219,6 +18452,24 @@ __metadata: languageName: node linkType: hard +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 4d211042cc3d73a718c21ac6c4e7d7a0363e184be6a5ad25c8a1502e49df6d0a0253979e3d50dbdd3f60ef6c6c58d756b5d66ac1e05cda9cacd2e9fc59e3876a + languageName: node + linkType: hard + +"simple-get@npm:^3.0.3": + version: 3.1.1 + resolution: "simple-get@npm:3.1.1" + dependencies: + decompress-response: ^4.2.0 + once: ^1.3.1 + simple-concat: ^1.0.0 + checksum: 80195e70bf171486e75c31e28e5485468195cc42f85940f8b45c4a68472160144d223eb4d07bc82ef80cb974b7c401db021a540deb2d34ac4b3b8883da2d6401 + languageName: node + linkType: hard + "simple-swizzle@npm:^0.2.2": version: 0.2.2 resolution: "simple-swizzle@npm:0.2.2" @@ -18555,6 +18806,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^1.0.1": + version: 1.0.2 + resolution: "string-width@npm:1.0.2" + dependencies: + code-point-at: ^1.0.0 + is-fullwidth-code-point: ^1.0.0 + strip-ansi: ^3.0.0 + checksum: 5c79439e95bc3bd7233a332c5f5926ab2ee90b23816ed4faa380ce3b2576d7800b0a5bb15ae88ed28737acc7ea06a518c2eef39142dd727adad0e45c776cd37e + languageName: node + linkType: hard + "string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" @@ -18778,6 +19040,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": + version: 3.0.1 + resolution: "strip-ansi@npm:3.0.1" + dependencies: + ansi-regex: ^2.0.0 + checksum: 9b974de611ce5075c70629c00fa98c46144043db92ae17748fb780f706f7a789e9989fd10597b7c2053ae8d1513fd707816a91f1879b2f71e6ac0b6a863db465 + languageName: node + linkType: hard + "strip-ansi@npm:^7.0.1": version: 7.0.1 resolution: "strip-ansi@npm:7.0.1" @@ -18839,6 +19110,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + "strip-outer@npm:^1.0.1": version: 1.0.1 resolution: "strip-outer@npm:1.0.1" @@ -18955,6 +19233,31 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^2.0.0": + version: 2.1.5 + resolution: "tar-fs@npm:2.1.5" + dependencies: + chownr: ^1.1.1 + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^2.1.4 + checksum: c8322c51d676509371273eeb19d65f919a041992e06dee67fe49d7295f16ce9af3ebf528c2480d5aacff97f114faba8bade1ac6c1fd4ca13a74ed8e97e856439 + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: ^4.0.3 + end-of-stream: ^1.4.1 + fs-constants: ^1.0.0 + inherits: ^2.0.3 + readable-stream: ^3.1.1 + checksum: 699831a8b97666ef50021c767f84924cfee21c142c2eb0e79c63254e140e6408d6d55a065a2992548e72b06de39237ef2b802b99e3ece93ca3904a37622a66f3 + languageName: node + linkType: hard + "tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.12, tar@npm:^6.1.2": version: 6.2.1 resolution: "tar@npm:6.2.1" @@ -20400,6 +20703,13 @@ __metadata: languageName: node linkType: hard +"which-pm-runs@npm:^1.0.0": + version: 1.1.0 + resolution: "which-pm-runs@npm:1.1.0" + checksum: 39a56ee50886fb33ec710e3b36dc9fe3d0096cac44850d9ca0c6186c4cb824d6c8125f013e0562e7c94744e1e8e4a6ab695592cdb12555777c7a4368143d822c + languageName: node + linkType: hard + "which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.11": version: 1.1.11 resolution: "which-typed-array@npm:1.1.11" @@ -20515,7 +20825,7 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.5": +"wide-align@npm:^1.1.0, wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" dependencies: @@ -20656,6 +20966,7 @@ __metadata: redux: 4.2.1 redux-logger: 3.0.6 redux-thunk: 2.4.2 + registry-js: 1.16.1 rimraf: 5.0.8 sinon: 17.0.2 sort-json: 2.0.1