Skip to content

Commit

Permalink
Make isProtocolHandlerRegistered cross-platform
Browse files Browse the repository at this point in the history
  • Loading branch information
toebeann committed Jan 27, 2024
1 parent 865f312 commit 48448dc
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 74 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,15 @@
"glob": "^10.3.10",
"gradient-string": "^2.0.2",
"open": "^10.0.3",
"protocol-registry": "^1.4.1",
"readline-sync": "^1.4.10",
"terminal-link": "^3.0.0",
"ts-pattern": "^5.0.6",
"typescript": "^5.3.3",
"wrap-ansi": "^9.0.0",
"zod": "^3.22.4"
},
"optionalDependencies": {
"registry-js": "^1.15.1"
}
}
29 changes: 29 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 7 additions & 74 deletions src/utils/isProtocolHandlerRegistered.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,13 @@
import { homedir } from "node:os";
import { join } from "node:path";
import { match, P } from "ts-pattern";
import { z } from "zod";
import { exec } from "../fs/exec.js";

const launchServicesHandlersSchema = z.object({
LSHandlerURLScheme: z.string().optional(),
}).passthrough().array();

export const launchServicesSecureSchema = z.union([
launchServicesHandlersSchema,
z.object({
LSHandlers: launchServicesHandlersSchema,
}).passthrough(),
]);

const parseLaunchServices = (
launchServices: z.infer<typeof launchServicesSecureSchema>,
) =>
match(launchServices)
.returnType<z.infer<typeof launchServicesHandlersSchema>>()
.with(P.array(), (launchServices) => launchServices)
.otherwise((obj) => obj.LSHandlers);
import ProtocolRegistry from "protocol-registry";
import { match } from "ts-pattern";
import { isProtocolHandlerRegistered as win } from "./windows/isProtocolHandlerRegistered.js";

/**
* Determines whether a handler for a given protocol is registered.
*
* Checks the launch services registered in
* `$HOME/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`
* first, and if not found there falls back checking with `open`, which opens
* the handler for the given protocol in the background if one is registered.
*
* @param protocol The protocol to check for.
*/
export const isProtocolHandlerRegistered = async (protocol: string) => {
let plutilOutput: string | undefined;
try {
const { stdout } = await exec(
`plutil -convert json -o - "${
join(
homedir(),
"Library",
"Preferences",
"com.apple.LaunchServices",
"com.apple.launchservices.secure.plist",
)
}"`,
);
plutilOutput = stdout.trim();
} catch {}

if (plutilOutput) {
const launchServices = launchServicesHandlersSchema.safeParse(
JSON.parse(plutilOutput),
);
if (
launchServices.success &&
parseLaunchServices(launchServices.data).map((entry) =>
entry.LSHandlerURLScheme
).filter(
Boolean,
).includes(protocol)
) {
return true;
}
}

try {
await exec(`open --background ${protocol}://`);
return true;
} catch {
try {
await exec(`open ${protocol}://`);
return true;
} catch {
return false;
}
}
};
export const isProtocolHandlerRegistered = match(process.platform)
.returnType<(protocol: string) => Promise<boolean>>()
.with("win32", () => (protocol) => Promise.resolve(win(protocol)))
.otherwise(() => ProtocolRegistry.checkifExists);
10 changes: 10 additions & 0 deletions src/utils/windows/isProtocolHandlerRegistered.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { enumerateValues, HKEY, RegistryValueType } from "registry-js";

export const isProtocolHandlerRegistered = (protocol: string) =>
enumerateValues(HKEY.HKEY_CLASSES_ROOT, protocol)
.concat(
enumerateValues(HKEY.HKEY_CURRENT_USER, `Software\\Classes\\${protocol}`),
)
.some((entry) =>
entry.name === "URL Protocol" && entry.type === RegistryValueType.REG_SZ
);

0 comments on commit 48448dc

Please sign in to comment.