browser.ts 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import { execFileNoThrow } from './execFileNoThrow.js'
  2. function validateUrl(url: string): void {
  3. let parsedUrl: URL
  4. try {
  5. parsedUrl = new URL(url)
  6. } catch (_error) {
  7. throw new Error(`Invalid URL format: ${url}`)
  8. }
  9. // Validate URL protocol for security
  10. if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
  11. throw new Error(
  12. `Invalid URL protocol: must use http:// or https://, got ${parsedUrl.protocol}`,
  13. )
  14. }
  15. }
  16. /**
  17. * Open a file or folder path using the system's default handler.
  18. * Uses `open` on macOS, `explorer` on Windows, `xdg-open` on Linux.
  19. */
  20. export async function openPath(path: string): Promise<boolean> {
  21. try {
  22. const platform = process.platform
  23. if (platform === 'win32') {
  24. const { code } = await execFileNoThrow('explorer', [path])
  25. return code === 0
  26. }
  27. const command = platform === 'darwin' ? 'open' : 'xdg-open'
  28. const { code } = await execFileNoThrow(command, [path])
  29. return code === 0
  30. } catch (_) {
  31. return false
  32. }
  33. }
  34. export async function openBrowser(url: string): Promise<boolean> {
  35. try {
  36. // Parse and validate the URL
  37. validateUrl(url)
  38. const browserEnv = process.env.BROWSER
  39. const platform = process.platform
  40. if (platform === 'win32') {
  41. if (browserEnv) {
  42. // browsers require shell, else they will treat this as a file:/// handle
  43. const { code } = await execFileNoThrow(browserEnv, [`"${url}"`])
  44. return code === 0
  45. }
  46. const { code } = await execFileNoThrow(
  47. 'rundll32',
  48. ['url,OpenURL', url],
  49. {},
  50. )
  51. return code === 0
  52. } else {
  53. const command =
  54. browserEnv || (platform === 'darwin' ? 'open' : 'xdg-open')
  55. const { code } = await execFileNoThrow(command, [url])
  56. return code === 0
  57. }
  58. } catch (_) {
  59. return false
  60. }
  61. }