diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c8c45..3cc114e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * BREAKING CHANGE: Require Node.js 18.17 or newer * feat: Support for arm/arm64 + * fix: Fixed order of child key events when parent key is deleted # v2.0.4 (July 2, 2024) diff --git a/package.json b/package.json index d2c8eef..d0d0e73 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@biomejs/biome": "1.8.3", "@rollup/plugin-typescript": "11.1.6", "@types/node": "20.14.10", - "@vitest/coverage-v8": "2.0.2", + "@vitest/coverage-v8": "2.0.3", "esbuild": "0.23.0", "lefthook": "1.7.2", "prebuildify": "6.0.1", @@ -59,7 +59,7 @@ "rollup-plugin-esbuild": "6.1.1", "tslib": "2.6.3", "typescript": "5.5.3", - "vitest": "2.0.2" + "vitest": "2.0.3" }, "homepage": "https://github.com/tidev/winreglib", "bugs": "https://github.com/tidev/winreglib/issues", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4e3f8a..2e066ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: 20.14.10 version: 20.14.10 '@vitest/coverage-v8': - specifier: 2.0.2 - version: 2.0.2(vitest@2.0.2(@types/node@20.14.10)) + specifier: 2.0.3 + version: 2.0.3(vitest@2.0.3(@types/node@20.14.10)) esbuild: specifier: 0.23.0 version: 0.23.0 @@ -66,8 +66,8 @@ importers: specifier: 5.5.3 version: 5.5.3 vitest: - specifier: 2.0.2 - version: 2.0.2(@types/node@20.14.10) + specifier: 2.0.3 + version: 2.0.3(@types/node@20.14.10) packages: @@ -584,28 +584,28 @@ packages: '@types/node@20.14.10': resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} - '@vitest/coverage-v8@2.0.2': - resolution: {integrity: sha512-iA8eb4PMid3bMc++gfQSTvYE1QL//fC8pz+rKsTUDBFjdDiy/gH45hvpqyDu5K7FHhvgG0GNNCJzTMMSFKhoxg==} + '@vitest/coverage-v8@2.0.3': + resolution: {integrity: sha512-53d+6jXFdYbasXBmsL6qaGIfcY5eBQq0sP57AjdasOcSiGNj4qxkkpDKIitUNfjxcfAfUfQ8BD0OR2fSey64+g==} peerDependencies: - vitest: 2.0.2 + vitest: 2.0.3 - '@vitest/expect@2.0.2': - resolution: {integrity: sha512-nKAvxBYqcDugYZ4nJvnm5OR8eDJdgWjk4XM9owQKUjzW70q0icGV2HVnQOyYsp906xJaBDUXw0+9EHw2T8e0mQ==} + '@vitest/expect@2.0.3': + resolution: {integrity: sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==} - '@vitest/pretty-format@2.0.2': - resolution: {integrity: sha512-SBCyOXfGVvddRd9r2PwoVR0fonQjh9BMIcBMlSzbcNwFfGr6ZhOhvBzurjvi2F4ryut2HcqiFhNeDVGwru8tLg==} + '@vitest/pretty-format@2.0.3': + resolution: {integrity: sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==} - '@vitest/runner@2.0.2': - resolution: {integrity: sha512-OCh437Vi8Wdbif1e0OvQcbfM3sW4s2lpmOjAE7qfLrpzJX2M7J1IQlNvEcb/fu6kaIB9n9n35wS0G2Q3en5kHg==} + '@vitest/runner@2.0.3': + resolution: {integrity: sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==} - '@vitest/snapshot@2.0.2': - resolution: {integrity: sha512-Yc2ewhhZhx+0f9cSUdfzPRcsM6PhIb+S43wxE7OG0kTxqgqzo8tHkXFuFlndXeDMp09G3sY/X5OAo/RfYydf1g==} + '@vitest/snapshot@2.0.3': + resolution: {integrity: sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==} - '@vitest/spy@2.0.2': - resolution: {integrity: sha512-MgwJ4AZtCgqyp2d7WcQVE8aNG5vQ9zu9qMPYQHjsld/QVsrvg78beNrXdO4HYkP0lDahCO3P4F27aagIag+SGQ==} + '@vitest/spy@2.0.3': + resolution: {integrity: sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==} - '@vitest/utils@2.0.2': - resolution: {integrity: sha512-pxCY1v7kmOCWYWjzc0zfjGTA3Wmn8PKnlPvSrsA643P1NHl1fOyXj2Q9SaNlrlFE+ivCsxM80Ov3AR82RmHCWQ==} + '@vitest/utils@2.0.3': + resolution: {integrity: sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==} abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} @@ -1362,8 +1362,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite-node@2.0.2: - resolution: {integrity: sha512-w4vkSz1Wo+NIQg8pjlEn0jQbcM/0D+xVaYjhw3cvarTanLLBh54oNiRbsT8PNK5GfuST0IlVXjsNRoNlqvY/fw==} + vite-node@2.0.3: + resolution: {integrity: sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1395,15 +1395,15 @@ packages: terser: optional: true - vitest@2.0.2: - resolution: {integrity: sha512-WlpZ9neRIjNBIOQwBYfBSr0+of5ZCbxT2TVGKW4Lv0c8+srCFIiRdsP7U009t8mMn821HQ4XKgkx5dVWpyoyLw==} + vitest@2.0.3: + resolution: {integrity: sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.0.2 - '@vitest/ui': 2.0.2 + '@vitest/browser': 2.0.3 + '@vitest/ui': 2.0.3 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1778,7 +1778,7 @@ snapshots: dependencies: undici-types: 5.26.5 - '@vitest/coverage-v8@2.0.2(vitest@2.0.2(@types/node@20.14.10))': + '@vitest/coverage-v8@2.0.3(vitest@2.0.3(@types/node@20.14.10))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -1793,39 +1793,39 @@ snapshots: strip-literal: 2.1.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.0.2(@types/node@20.14.10) + vitest: 2.0.3(@types/node@20.14.10) transitivePeerDependencies: - supports-color - '@vitest/expect@2.0.2': + '@vitest/expect@2.0.3': dependencies: - '@vitest/spy': 2.0.2 - '@vitest/utils': 2.0.2 + '@vitest/spy': 2.0.3 + '@vitest/utils': 2.0.3 chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/pretty-format@2.0.2': + '@vitest/pretty-format@2.0.3': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.0.2': + '@vitest/runner@2.0.3': dependencies: - '@vitest/utils': 2.0.2 + '@vitest/utils': 2.0.3 pathe: 1.1.2 - '@vitest/snapshot@2.0.2': + '@vitest/snapshot@2.0.3': dependencies: - '@vitest/pretty-format': 2.0.2 + '@vitest/pretty-format': 2.0.3 magic-string: 0.30.10 pathe: 1.1.2 - '@vitest/spy@2.0.2': + '@vitest/spy@2.0.3': dependencies: tinyspy: 3.0.0 - '@vitest/utils@2.0.2': + '@vitest/utils@2.0.3': dependencies: - '@vitest/pretty-format': 2.0.2 + '@vitest/pretty-format': 2.0.3 estree-walker: 3.0.3 loupe: 3.1.1 tinyrainbow: 1.2.0 @@ -2631,7 +2631,7 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@2.0.2(@types/node@20.14.10): + vite-node@2.0.3(@types/node@20.14.10): dependencies: cac: 6.7.14 debug: 4.3.5 @@ -2657,15 +2657,15 @@ snapshots: '@types/node': 20.14.10 fsevents: 2.3.3 - vitest@2.0.2(@types/node@20.14.10): + vitest@2.0.3(@types/node@20.14.10): dependencies: '@ampproject/remapping': 2.3.0 - '@vitest/expect': 2.0.2 - '@vitest/pretty-format': 2.0.2 - '@vitest/runner': 2.0.2 - '@vitest/snapshot': 2.0.2 - '@vitest/spy': 2.0.2 - '@vitest/utils': 2.0.2 + '@vitest/expect': 2.0.3 + '@vitest/pretty-format': 2.0.3 + '@vitest/runner': 2.0.3 + '@vitest/snapshot': 2.0.3 + '@vitest/spy': 2.0.3 + '@vitest/utils': 2.0.3 chai: 5.1.1 debug: 4.3.5 execa: 8.0.1 @@ -2676,7 +2676,7 @@ snapshots: tinypool: 1.0.0 tinyrainbow: 1.2.0 vite: 5.3.1(@types/node@20.14.10) - vite-node: 2.0.2(@types/node@20.14.10) + vite-node: 2.0.3(@types/node@20.14.10) why-is-node-running: 2.2.2 optionalDependencies: '@types/node': 20.14.10 diff --git a/src/watchman.cpp b/src/watchman.cpp index 87ba715..b9fbfea 100644 --- a/src/watchman.cpp +++ b/src/watchman.cpp @@ -203,12 +203,12 @@ void Watchman::dispatch() { * Prints the watcher tree for debugging. */ void Watchman::printTree() { - std::wstringstream wss(L""); - std::wstring line; - root->print(wss); - while (std::getline(wss, line, L'\n')) { - WLOG_DEBUG("Watchman::printTree", line) - } + // std::wstringstream wss(L""); + // std::wstring line; + // root->print(wss); + // while (std::getline(wss, line, L'\n')) { + // WLOG_DEBUG("Watchman::printTree", line) + // } } /** diff --git a/src/watchnode.cpp b/src/watchnode.cpp index 6cfa5a1..14bd3fb 100644 --- a/src/watchnode.cpp +++ b/src/watchnode.cpp @@ -81,6 +81,8 @@ std::u16string WatchNode::getKey() { /** * Attempts to open this node's registry key and watch it. + * + * Returns true if something changed. */ bool WatchNode::load(CallbackQueue* pending) { bool result = false; @@ -99,8 +101,6 @@ bool WatchNode::load(CallbackQueue* pending) { LOG_DEBUG_1("WatchNode::load", L"\"%ls\" hkey is still valid", name.c_str()) } else { // no longer valid! - LOG_DEBUG_1("WatchNode::load", L"\"%ls\" hkey is no longer valid", name.c_str()) - unload(pending); result = true; } } else { @@ -116,9 +116,7 @@ bool WatchNode::load(CallbackQueue* pending) { } for (auto const& it : subkeys) { - if (it.second->load(pending)) { - result = true; - } + it.second->load(pending); } result = true; @@ -146,6 +144,17 @@ bool WatchNode::onChange() { if (hkey) { if (watch(&pending)) { + // hkey should be valid, check if subkeys are ok + LOG_DEBUG_1("WatchNode::onChange", L"Checking if subkeys under \"%ls\" are still valid", name.c_str()) + for (auto const& it : subkeys) { + HKEY tmp; + LSTATUS status = ::RegOpenKeyW(hkey, it.second->name.c_str(), &tmp); + if (status != ERROR_SUCCESS) { + LOG_DEBUG_1("WatchNode::onChange", L"\"%ls\" hkey is no longer valid", it.second->name.c_str()) + it.second->unload(&pending); + } + } + PUSH_CALLBACK(pending, "change", getKey(), listeners) } @@ -240,6 +249,7 @@ void WatchNode::removeListener(napi_value listener) { * Closes this node's registry key handle and its subkey's key handles. */ void WatchNode::unload(CallbackQueue* pending) { + LOG_DEBUG_1("WatchNode::unload", L"Checking subkeys under \"%ls\" hkey", name.c_str()) for (auto const& it : subkeys) { it.second->unload(pending); } @@ -257,7 +267,9 @@ void WatchNode::unload(CallbackQueue* pending) { } /** - * Wires up the change notification event. + * Wires up the Windows Registry change notification event asynchronously. + * + * Returns true if the key is valid and the watcher was successfully registered. */ bool WatchNode::watch(CallbackQueue* pending) { if (hkey) { diff --git a/test/watch.test.ts b/test/watch.test.ts index f6ea116..5d81972 100644 --- a/test/watch.test.ts +++ b/test/watch.test.ts @@ -68,7 +68,7 @@ describe('watch()', () => { reject(e); } }); - setTimeout(() => reg('add', 'HKCU\\Software\\winreglib\\foo'), 500); + setTimeout(() => reg('add', 'HKCU\\Software\\winreglib\\foo'), 250); }); handle.stop(); @@ -134,7 +134,7 @@ describe('watch()', () => { '/d', 'bar' ), - 500 + 250 ); }); } finally { @@ -194,7 +194,7 @@ describe('watch()', () => { }); setTimeout( () => reg('delete', 'HKCU\\Software\\winreglib\\foo', '/f'), - 500 + 250 ); break; case 3: @@ -204,7 +204,7 @@ describe('watch()', () => { }); setTimeout( () => reg('add', 'HKCU\\Software\\winreglib\\foo'), - 500 + 250 ); break; case 4: @@ -219,7 +219,7 @@ describe('watch()', () => { reject(e); } }); - setTimeout(() => reg('add', 'HKCU\\Software\\winreglib\\foo'), 500); + setTimeout(() => reg('add', 'HKCU\\Software\\winreglib\\foo'), 250); }); } finally { handle.stop(); @@ -289,7 +289,7 @@ describe('watch()', () => { '/d', 'hello3' ); - }, 1000); + }, 250); break; case 2: expect(evt).toMatchObject({ @@ -298,7 +298,7 @@ describe('watch()', () => { }); setTimeout(() => { reg('delete', 'HKCU\\Software\\winreglib', '/f'); - }, 1000); + }, 250); break; case 3: expect(evt).toMatchObject({ @@ -313,10 +313,10 @@ describe('watch()', () => { } }); - setTimeout(() => reg('add', 'HKCU\\Software\\winreglib\\foo'), 500); + setTimeout(() => reg('add', 'HKCU\\Software\\winreglib\\foo'), 250); setTimeout( () => reg('add', 'HKCU\\Software\\winreglib\\foo\\bar\\baz\\wiz'), - 2000 + 250 ); }); } finally { @@ -354,7 +354,7 @@ describe('watch()', () => { }); setTimeout( () => reg('delete', 'HKCU\\Software\\winreglib\\foo', '/f'), - 500 + 250 ); }); handle.stop(); @@ -398,7 +398,7 @@ describe('watch()', () => { }); setTimeout( () => reg('delete', 'HKCU\\Software\\winreglib\\foo', '/f'), - 500 + 250 ); }); handle.stop(); @@ -410,7 +410,7 @@ describe('watch()', () => { } ); - it.only('should survive the gauntlet', { timeout: 15000 }, async () => { + it('should survive the gauntlet', { timeout: 150000 }, async () => { reg('delete', 'HKCU\\Software\\winreglib', '/f'); reg('add', 'HKCU\\Software\\winreglib'); @@ -452,17 +452,17 @@ describe('watch()', () => { reg('delete', 'HKCU\\Software\\winreglib\\foo\\bar', '/f'); break; case 3: - expect(handleName).toBe('fooHandle'); + expect(handleName).toBe('barHandle'); expect(evt).toMatchObject({ - type: 'change', - key: 'HKEY_CURRENT_USER\\SOFTWARE\\winreglib\\foo' + type: 'delete', + key: 'HKEY_CURRENT_USER\\SOFTWARE\\winreglib\\foo\\bar' }); break; case 4: - expect(handleName).toBe('barHandle'); + expect(handleName).toBe('fooHandle'); expect(evt).toMatchObject({ - type: 'delete', - key: 'HKEY_CURRENT_USER\\SOFTWARE\\winreglib\\foo\\bar' + type: 'change', + key: 'HKEY_CURRENT_USER\\SOFTWARE\\winreglib\\foo' }); reg( 'add', @@ -578,7 +578,7 @@ describe('watch()', () => { bazHandle.on('change', evt => fn(evt, 'bazHandle')); setTimeout( () => reg('add', 'HKCU\\Software\\winreglib\\foo\\bar'), - 500 + 250 ); }); } finally {