mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
fix: satisfy Obsidian plugin scan (undescribed directive, popout-window timers)
- src/utils/git-blob-sha.ts: add a required description to the sonarjs/hashing eslint-disable comment. - src/services/github-service.ts, src/settings.ts: use window.setTimeout()/window.clearTimeout() instead of the bare globals, so timers still fire correctly in Obsidian popout windows. connectionTestTimer is now typed as plain `number` (window.setTimeout's DOM return type) instead of ReturnType<typeof window.setTimeout>, which resolves to Node's Timeout under this repo's tsconfig due to @types/node's global augmentation colliding with the DOM lib. - tests/setup.ts: the node-env `window` polyfill only stubbed setInterval/clearInterval; add setTimeout/clearTimeout delegating to the real globals so vi.useFakeTimers() still controls them.
This commit is contained in:
parent
1a369b36ed
commit
09bdf0c0c7
4 changed files with 17 additions and 12 deletions
|
|
@ -148,7 +148,7 @@ export class GitHubService extends BaseGitService implements GitServiceInterface
|
||||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||||
const looksStale = /does not exist in tree|does not match|expectedHeadOid/i.test(errorMessage);
|
const looksStale = /does not exist in tree|does not match|expectedHeadOid/i.test(errorMessage);
|
||||||
if (!looksStale || attempt === maxAttempts) throw e;
|
if (!looksStale || attempt === maxAttempts) throw e;
|
||||||
await new Promise(resolve => setTimeout(resolve, 500 * attempt));
|
await new Promise(resolve => window.setTimeout(resolve, 500 * attempt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unreachable: the loop always returns or throws.
|
// Unreachable: the loop always returns or throws.
|
||||||
|
|
@ -178,7 +178,7 @@ export class GitHubService extends BaseGitService implements GitServiceInterface
|
||||||
const shaByPath = new Map(freshTree.map(e => [e.path, e.sha]));
|
const shaByPath = new Map(freshTree.map(e => [e.path, e.sha]));
|
||||||
const results = items.map((item, i) => ({ path: item.path, sha: shaByPath.get(fullPaths[i] as string) }));
|
const results = items.map((item, i) => ({ path: item.path, sha: shaByPath.get(fullPaths[i] as string) }));
|
||||||
if (results.every(r => r.sha) || attempt === 3) return results;
|
if (results.every(r => r.sha) || attempt === 3) return results;
|
||||||
await new Promise(resolve => setTimeout(resolve, 500 * attempt));
|
await new Promise(resolve => window.setTimeout(resolve, 500 * attempt));
|
||||||
}
|
}
|
||||||
// Unreachable: the loop always returns on its last iteration.
|
// Unreachable: the loop always returns on its last iteration.
|
||||||
throw new Error('pushBatch: exhausted retries reading back blob shas');
|
throw new Error('pushBatch: exhausted retries reading back blob shas');
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ const CONNECTION_TEST_DEBOUNCE_MS = 800;
|
||||||
export class GitLabSyncSettingTab extends PluginSettingTab {
|
export class GitLabSyncSettingTab extends PluginSettingTab {
|
||||||
plugin: GitLabFilesPush;
|
plugin: GitLabFilesPush;
|
||||||
private statusBadgeEl: HTMLElement | null = null;
|
private statusBadgeEl: HTMLElement | null = null;
|
||||||
private connectionTestTimer: ReturnType<typeof setTimeout> | null = null;
|
private connectionTestTimer: number | null = null;
|
||||||
private unsubscribeConnectionStatus: (() => void) | null = null;
|
private unsubscribeConnectionStatus: (() => void) | null = null;
|
||||||
|
|
||||||
constructor(app: App, plugin: GitLabFilesPush) {
|
constructor(app: App, plugin: GitLabFilesPush) {
|
||||||
|
|
@ -120,7 +120,7 @@ export class GitLabSyncSettingTab extends PluginSettingTab {
|
||||||
this.unsubscribeConnectionStatus?.();
|
this.unsubscribeConnectionStatus?.();
|
||||||
this.unsubscribeConnectionStatus = null;
|
this.unsubscribeConnectionStatus = null;
|
||||||
if (this.connectionTestTimer) {
|
if (this.connectionTestTimer) {
|
||||||
clearTimeout(this.connectionTestTimer);
|
window.clearTimeout(this.connectionTestTimer);
|
||||||
this.connectionTestTimer = null;
|
this.connectionTestTimer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -153,11 +153,6 @@ export class GitLabSyncSettingTab extends PluginSettingTab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuilding the whole settings tab (renderSettings) to refresh the badge
|
|
||||||
// would empty and recreate every field, stealing focus mid-typing. The
|
|
||||||
// badge element is instead created once per renderSettings pass and
|
|
||||||
// updated in place by setStatusBadge(), driven by the plugin's shared
|
|
||||||
// connection status (see main.ts) so it stays in sync with the status bar.
|
|
||||||
// Persistent (until dismissed) banner surfacing the current version's notable
|
// Persistent (until dismissed) banner surfacing the current version's notable
|
||||||
// highlights right at the top of the settings tab, so users who dismissed or
|
// highlights right at the top of the settings tab, so users who dismissed or
|
||||||
// never saw the WhatsNewModal (see main.ts) can still find them. Separate
|
// never saw the WhatsNewModal (see main.ts) can still find them. Separate
|
||||||
|
|
@ -193,6 +188,11 @@ export class GitLabSyncSettingTab extends PluginSettingTab {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rebuilding the whole settings tab (renderSettings) to refresh the badge
|
||||||
|
// would empty and recreate every field, stealing focus mid-typing. The
|
||||||
|
// badge element is instead created once per renderSettings pass and
|
||||||
|
// updated in place by setStatusBadge(), driven by the plugin's shared
|
||||||
|
// connection status (see main.ts) so it stays in sync with the status bar.
|
||||||
private renderConnectionStatus(containerEl: HTMLElement): void {
|
private renderConnectionStatus(containerEl: HTMLElement): void {
|
||||||
this.statusBadgeEl = containerEl.createDiv({ cls: 'gfs-connection-status' });
|
this.statusBadgeEl = containerEl.createDiv({ cls: 'gfs-connection-status' });
|
||||||
this.unsubscribeConnectionStatus?.();
|
this.unsubscribeConnectionStatus?.();
|
||||||
|
|
@ -219,9 +219,9 @@ export class GitLabSyncSettingTab extends PluginSettingTab {
|
||||||
// don't hit the remote API on every character typed.
|
// don't hit the remote API on every character typed.
|
||||||
private scheduleConnectionTest(): void {
|
private scheduleConnectionTest(): void {
|
||||||
if (this.connectionTestTimer) {
|
if (this.connectionTestTimer) {
|
||||||
clearTimeout(this.connectionTestTimer);
|
window.clearTimeout(this.connectionTestTimer);
|
||||||
}
|
}
|
||||||
this.connectionTestTimer = setTimeout(() => {
|
this.connectionTestTimer = window.setTimeout(() => {
|
||||||
this.connectionTestTimer = null;
|
this.connectionTestTimer = null;
|
||||||
void this.plugin.testConnection();
|
void this.plugin.testConnection();
|
||||||
}, CONNECTION_TEST_DEBOUNCE_MS);
|
}, CONNECTION_TEST_DEBOUNCE_MS);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export async function gitBlobSha(content: string | ArrayBuffer): Promise<string>
|
||||||
|
|
||||||
// SHA-1 isn't for security here — it's required because it's git's own
|
// SHA-1 isn't for security here — it's required because it's git's own
|
||||||
// object-hashing algorithm, so this must match `git hash-object` exactly.
|
// object-hashing algorithm, so this must match `git hash-object` exactly.
|
||||||
// eslint-disable-next-line sonarjs/hashing
|
// eslint-disable-next-line sonarjs/hashing -- SHA-1 is required here to match git's own object-hashing algorithm, not used for security
|
||||||
const digest = await crypto.subtle.digest('SHA-1', combined);
|
const digest = await crypto.subtle.digest('SHA-1', combined);
|
||||||
return Array.from(new Uint8Array(digest)).map(b => b.toString(16).padStart(2, '0')).join('');
|
return Array.from(new Uint8Array(digest)).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ if (typeof window === 'undefined') {
|
||||||
(globalThis as unknown as { window: unknown }).window = {
|
(globalThis as unknown as { window: unknown }).window = {
|
||||||
setInterval: vi.fn(),
|
setInterval: vi.fn(),
|
||||||
clearInterval: vi.fn(),
|
clearInterval: vi.fn(),
|
||||||
|
// Delegate to the global timers (not bound methods captured once) so
|
||||||
|
// vi.useFakeTimers()/vi.useRealTimers() — which patch globalThis — keep
|
||||||
|
// controlling window.setTimeout/clearTimeout too.
|
||||||
|
setTimeout: (handler: (...args: unknown[]) => void, timeout?: number) => globalThis.setTimeout(handler, timeout),
|
||||||
|
clearTimeout: (handle?: ReturnType<typeof globalThis.setTimeout>) => globalThis.clearTimeout(handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue