electron-windows-hello 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +87 -2
- package/electron-windows-hello.win32-arm64-msvc.node +0 -0
- package/electron-windows-hello.win32-x64-msvc.node +0 -0
- package/index.cjs +112 -0
- package/index.d.ts +31 -0
- package/package.json +50 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 huhuanming
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,88 @@
|
|
|
1
|
-
#
|
|
1
|
+
# electron-windows-hello
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Windows Hello verification associated with an Electron `BrowserWindow`.
|
|
4
|
+
Independent Node-API implementation for Windows x64 and ARM64, with no runtime npm dependencies.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install electron-windows-hello
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## API
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import {
|
|
14
|
+
Availability,
|
|
15
|
+
VerificationResult,
|
|
16
|
+
allowSetForegroundWindow,
|
|
17
|
+
checkAvailability,
|
|
18
|
+
requestVerification,
|
|
19
|
+
} from "electron-windows-hello";
|
|
20
|
+
|
|
21
|
+
const availability = await checkAvailability();
|
|
22
|
+
const result = await requestVerification({
|
|
23
|
+
windowHandle: mainWindow.getNativeWindowHandle(),
|
|
24
|
+
message: "OneKey requests verification to unlock your wallet",
|
|
25
|
+
});
|
|
26
|
+
const verified = result === VerificationResult.Verified;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`checkAvailability()` resolves to a value in `Availability`: `Available`, `DeviceNotPresent`, `NotConfiguredForUser`, `DisabledByPolicy`, or `DeviceBusy`.
|
|
30
|
+
It first probes the window-owned `IUserConsentVerifierInterop` interface at runtime, without showing a verification dialog. It returns `DeviceNotPresent` on unsupported operating systems and architectures, without loading a native binary. An unavailable interop interface or Windows API failure rejects the promise; applications should offer their normal password authentication instead.
|
|
31
|
+
|
|
32
|
+
`requestVerification({ windowHandle, message })` requires a fresh, 8-byte `Buffer` from `BrowserWindow.getNativeWindowHandle()` and a non-empty message. It resolves to a value in `VerificationResult`: `Verified`, `Canceled`, `DeviceBusy`, `DeviceNotPresent`, `NotConfiguredForUser`, `DisabledByPolicy`, or `RetriesExhausted`.
|
|
33
|
+
Only `Verified` authorizes the operation. An overlapping request returns `DeviceBusy` and does not share another request's authorization.
|
|
34
|
+
Invalid or destroyed owner windows and Windows API errors reject the promise. Native errors retain the Windows HRESULT in the error message.
|
|
35
|
+
|
|
36
|
+
`allowSetForegroundWindow(processId)` grants a specific process permission to set the foreground window, when Windows permits the caller to grant it. It returns whether Windows accepted the grant. Call it from the foreground Electron main process before dispatching verification to a utility process. Zero, wildcard, and non-integer process IDs are rejected.
|
|
37
|
+
|
|
38
|
+
## Electron utility process and focus
|
|
39
|
+
|
|
40
|
+
In the main process, restore and activate the actual owner window before dispatching:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
44
|
+
mainWindow.show();
|
|
45
|
+
mainWindow.focus();
|
|
46
|
+
allowSetForegroundWindow(helloProcess.pid);
|
|
47
|
+
helloProcess.postMessage({
|
|
48
|
+
message: "OneKey requests verification to unlock your wallet",
|
|
49
|
+
windowHandle: Array.from(mainWindow.getNativeWindowHandle()),
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Wait for the owner to be focused before dispatching. If `mainWindow.isFocused()` is false, defer verification until a user-initiated window activation. `show()` and `focus()` alone do not guarantee that Windows grants foreground permission.
|
|
54
|
+
|
|
55
|
+
In the utility process, reconstruct the handle with `Buffer.from(data.windowHandle)` and call `requestVerification` with that buffer. Always send a result or error back to the caller.
|
|
56
|
+
Do not convert the handle to a JavaScript number or discover the owner by querying an unrelated foreground window.
|
|
57
|
+
|
|
58
|
+
WinRT is initialized and awaited on a libuv worker, so verification does not block Electron's JavaScript thread. Windows controls foreground policy, title, icon, taskbar presentation, and the authentication method. An owner handle and foreground grant do not guarantee a particular icon or force focus against operating-system policy.
|
|
59
|
+
|
|
60
|
+
## Support
|
|
61
|
+
|
|
62
|
+
- Windows x64 and ARM64; both native binaries ship in the same package and are selected using `process.arch`.
|
|
63
|
+
- Node.js 20.17 or later, including Electron runtimes with Node-API 8 support.
|
|
64
|
+
- Windows 10 and 11 use the same window-owned `IUserConsentVerifierInterop::RequestVerificationForWindowAsync` path, selected by runtime interface availability rather than an OS version check. Microsoft's method documentation lists build 22000, while its [CsWinRT implementation](https://github.com/microsoft/CsWinRT/blob/master/src/cswinrt/strings/ComInteropHelpers.cs) and [Electron sample](https://github.com/microsoft/dynwinrt/tree/main/samples/js/windows-hello) indicate Windows 10 support. Windows 11 interaction has been manually verified; Windows 10 interaction testing is still pending, so no minimum Windows 10 build is claimed.
|
|
65
|
+
- There is no silent fallback to an unowned verification dialog. If availability fails, offer the application's normal password authentication. Cancellation, unsuccessful verification, and native errors never cause an automatic retry; a new Hello attempt must be explicitly initiated by the user.
|
|
66
|
+
- No credential storage, account management, key generation, signing, PIN collection, or custom authentication UI.
|
|
67
|
+
|
|
68
|
+
## Development and release
|
|
69
|
+
|
|
70
|
+
Install Rust 1.88 or later and the Windows MSVC toolchain, then run:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npm ci --ignore-scripts
|
|
74
|
+
npm run build
|
|
75
|
+
npm test
|
|
76
|
+
npm run test:types
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Build a specific target with `npm run build -- x86_64-pc-windows-msvc` or `npm run build -- aarch64-pc-windows-msvc`.
|
|
80
|
+
CI builds and tests each architecture on a native Windows runner, then checks and packs the combined npm package. `npm pack` refuses missing binaries or a PE machine type that does not match the advertised architecture. Only the runtime entrypoint, declarations, native binaries, README, license, and package metadata are published.
|
|
81
|
+
|
|
82
|
+
With Electron installed in the development environment, `npm run test:electron` checks the real HWND and utility-process path without an authentication prompt. `npm run test:electron -- --interactive` opens a visible owner window and requests Windows Hello; verify focus, cancellation, multi-monitor placement, and taskbar presentation in a logged-in desktop session. Headless CI cannot establish those interaction results.
|
|
83
|
+
|
|
84
|
+
The publish workflow supports npm trusted publishing with provenance after configuring this repository and `.github/workflows/publish.yml` as a trusted publisher in npm. Alternatively, configure an npm publish token as the repository's `NPM_TOKEN` secret. Release tags must match the package version. Initial publication may require an authenticated local npm session.
|
|
85
|
+
|
|
86
|
+
## References
|
|
87
|
+
|
|
88
|
+
The API choice was informed by [Microsoft's desktop interoperability documentation](https://learn.microsoft.com/en-us/windows/win32/api/userconsentverifierinterop/nf-userconsentverifierinterop-iuserconsentverifierinterop-requestverificationforwindowasync) and investigation of [passport-desktop](https://github.com/MarkusJx/passport-desktop). This project has its own source, API, tests, and release pipeline; it does not include Passport account or cryptographic APIs.
|
|
Binary file
|
|
Binary file
|
package/index.cjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const availability = Object.freeze({
|
|
4
|
+
Available: "available",
|
|
5
|
+
DeviceNotPresent: "device_not_present",
|
|
6
|
+
NotConfiguredForUser: "not_configured_for_user",
|
|
7
|
+
DisabledByPolicy: "disabled_by_policy",
|
|
8
|
+
DeviceBusy: "device_busy",
|
|
9
|
+
});
|
|
10
|
+
const verificationResult = Object.freeze({
|
|
11
|
+
Verified: "verified",
|
|
12
|
+
DeviceNotPresent: "device_not_present",
|
|
13
|
+
NotConfiguredForUser: "not_configured_for_user",
|
|
14
|
+
DisabledByPolicy: "disabled_by_policy",
|
|
15
|
+
DeviceBusy: "device_busy",
|
|
16
|
+
RetriesExhausted: "retries_exhausted",
|
|
17
|
+
Canceled: "canceled",
|
|
18
|
+
});
|
|
19
|
+
let nativeBinding;
|
|
20
|
+
let verifying = false;
|
|
21
|
+
|
|
22
|
+
function errorWithCode(code, message) {
|
|
23
|
+
return Object.assign(new Error(message), { code });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function supportedPlatform() {
|
|
27
|
+
return (
|
|
28
|
+
process.platform === "win32" && ["x64", "arm64"].includes(process.arch)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function binding() {
|
|
33
|
+
if (!supportedPlatform()) {
|
|
34
|
+
throw errorWithCode(
|
|
35
|
+
"ERR_WINDOWS_HELLO_PLATFORM",
|
|
36
|
+
"Windows Hello requires Windows x64 or ARM64",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
nativeBinding ??= require(`./electron-windows-hello.win32-${process.arch}-msvc.node`);
|
|
40
|
+
return nativeBinding;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mapResult(values, value) {
|
|
44
|
+
if (!Number.isInteger(value) || value < 0 || value >= values.length) {
|
|
45
|
+
throw errorWithCode(
|
|
46
|
+
"ERR_WINDOWS_HELLO_RESULT",
|
|
47
|
+
"Windows returned an unknown verification result",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return values[value];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
exports.Availability = availability;
|
|
54
|
+
exports.VerificationResult = verificationResult;
|
|
55
|
+
exports.checkAvailability = async function checkAvailability() {
|
|
56
|
+
if (!supportedPlatform()) return availability.DeviceNotPresent;
|
|
57
|
+
return mapResult(
|
|
58
|
+
Object.values(availability),
|
|
59
|
+
await binding().checkAvailability(),
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
exports.requestVerification = async function requestVerification(options) {
|
|
63
|
+
if (
|
|
64
|
+
!options ||
|
|
65
|
+
!Buffer.isBuffer(options.windowHandle) ||
|
|
66
|
+
options.windowHandle.length !== 8
|
|
67
|
+
) {
|
|
68
|
+
throw errorWithCode(
|
|
69
|
+
"ERR_WINDOWS_HELLO_HANDLE",
|
|
70
|
+
"windowHandle must be the 8-byte Buffer returned by BrowserWindow.getNativeWindowHandle()",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
typeof options.message !== "string" ||
|
|
75
|
+
options.message.trim().length === 0
|
|
76
|
+
) {
|
|
77
|
+
throw errorWithCode(
|
|
78
|
+
"ERR_WINDOWS_HELLO_MESSAGE",
|
|
79
|
+
"A non-empty verification message is required",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (verifying) return verificationResult.DeviceBusy;
|
|
83
|
+
const native = binding();
|
|
84
|
+
verifying = true;
|
|
85
|
+
try {
|
|
86
|
+
return mapResult(
|
|
87
|
+
Object.values(verificationResult),
|
|
88
|
+
await native.requestVerification(
|
|
89
|
+
Buffer.from(options.windowHandle),
|
|
90
|
+
options.message,
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
} finally {
|
|
94
|
+
verifying = false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
exports.allowSetForegroundWindow = function allowSetForegroundWindow(
|
|
98
|
+
processId,
|
|
99
|
+
) {
|
|
100
|
+
if (
|
|
101
|
+
!Number.isInteger(processId) ||
|
|
102
|
+
processId <= 0 ||
|
|
103
|
+
processId >= 0xffffffff
|
|
104
|
+
) {
|
|
105
|
+
throw errorWithCode(
|
|
106
|
+
"ERR_WINDOWS_HELLO_PROCESS",
|
|
107
|
+
"A specific positive process ID is required",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (!supportedPlatform()) return false;
|
|
111
|
+
return binding().allowSetForegroundWindow(processId);
|
|
112
|
+
};
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const Availability: {
|
|
2
|
+
readonly Available: "available";
|
|
3
|
+
readonly DeviceNotPresent: "device_not_present";
|
|
4
|
+
readonly NotConfiguredForUser: "not_configured_for_user";
|
|
5
|
+
readonly DisabledByPolicy: "disabled_by_policy";
|
|
6
|
+
readonly DeviceBusy: "device_busy";
|
|
7
|
+
};
|
|
8
|
+
export type Availability = (typeof Availability)[keyof typeof Availability];
|
|
9
|
+
|
|
10
|
+
export declare const VerificationResult: {
|
|
11
|
+
readonly Verified: "verified";
|
|
12
|
+
readonly DeviceNotPresent: "device_not_present";
|
|
13
|
+
readonly NotConfiguredForUser: "not_configured_for_user";
|
|
14
|
+
readonly DisabledByPolicy: "disabled_by_policy";
|
|
15
|
+
readonly DeviceBusy: "device_busy";
|
|
16
|
+
readonly RetriesExhausted: "retries_exhausted";
|
|
17
|
+
readonly Canceled: "canceled";
|
|
18
|
+
};
|
|
19
|
+
export type VerificationResult =
|
|
20
|
+
(typeof VerificationResult)[keyof typeof VerificationResult];
|
|
21
|
+
|
|
22
|
+
export interface VerificationOptions {
|
|
23
|
+
windowHandle: import("node:buffer").Buffer;
|
|
24
|
+
message: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export declare function checkAvailability(): Promise<Availability>;
|
|
28
|
+
export declare function requestVerification(
|
|
29
|
+
options: VerificationOptions,
|
|
30
|
+
): Promise<VerificationResult>;
|
|
31
|
+
export declare function allowSetForegroundWindow(processId: number): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "electron-windows-hello",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Window-owned Windows Hello verification for Electron, with x64 and ARM64 prebuilds",
|
|
5
|
+
"main": "index.cjs",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./index.d.ts",
|
|
10
|
+
"default": "./index.cjs"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.cjs",
|
|
16
|
+
"index.d.ts",
|
|
17
|
+
"electron-windows-hello.win32-x64-msvc.node",
|
|
18
|
+
"electron-windows-hello.win32-arm64-msvc.node"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "node scripts/build.mjs",
|
|
22
|
+
"test": "node --test test/*.test.cjs",
|
|
23
|
+
"test:electron": "electron test/electron.cjs",
|
|
24
|
+
"test:types": "tsc --noEmit --strict --skipLibCheck --module node16 --moduleResolution node16 test/types.ts",
|
|
25
|
+
"prepack": "node scripts/check-package.mjs"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/huhuanming/electron-windows-hello.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": "https://github.com/huhuanming/electron-windows-hello/issues",
|
|
32
|
+
"homepage": "https://github.com/huhuanming/electron-windows-hello#readme",
|
|
33
|
+
"keywords": [
|
|
34
|
+
"electron",
|
|
35
|
+
"windows-hello",
|
|
36
|
+
"biometrics",
|
|
37
|
+
"windows",
|
|
38
|
+
"arm64"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"typescript": "^5.9.3"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20.17.0"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public",
|
|
50
|
+
"registry": "https://registry.npmjs.org"
|
|
51
|
+
}
|
|
52
|
+
}
|