@@ -23,6 +23,10 @@ import {
2323} from "../../utils/hyperlink.ts"
2424import { createHyperlinkExtension } from "../../utils/charmd-hyperlink-extension.ts"
2525import { handleError , ValidationError } from "../../utils/errors.ts"
26+ import {
27+ LINEAR_PRIVATE_UPLOAD_HOST ,
28+ LINEAR_UPLOAD_HOSTNAMES ,
29+ } from "../../const.ts"
2630
2731export const viewCommand = new Command ( )
2832 . name ( "view" )
@@ -476,11 +480,7 @@ export function extractLinearLinkInfo(
476480
477481 visit ( tree , "link" , ( node : Link ) => {
478482 // Only extract links to Linear uploads
479- if (
480- node . url &&
481- ( node . url . includes ( "uploads.linear.app" ) ||
482- node . url . includes ( "public.linear.app" ) )
483- ) {
483+ if ( node . url && getLinearUploadHost ( node . url ) ) {
484484 // Get link text from first child if it's a text node
485485 const textNode = node . children [ 0 ]
486486 const text = textNode && textNode . type === "text" ? textNode . value : null
@@ -533,6 +533,15 @@ export async function getUrlHash(url: string): Promise<string> {
533533 return encodeHex ( hashArray ) . substring ( 0 , 16 )
534534}
535535
536+ export function getLinearUploadHost ( url : string ) : string | null {
537+ try {
538+ const { hostname } = new URL ( url )
539+ return LINEAR_UPLOAD_HOSTNAMES . includes ( hostname ) ? hostname : null
540+ } catch {
541+ return null
542+ }
543+ }
544+
536545/**
537546 * download an image to the cache directory if not already cached
538547 * returns the local file path
@@ -556,7 +565,7 @@ async function downloadImage(
556565 }
557566
558567 const headers : Record < string , string > = { }
559- if ( url . includes ( "uploads.linear.app" ) ) {
568+ if ( getLinearUploadHost ( url ) === LINEAR_PRIVATE_UPLOAD_HOST ) {
560569 const apiKey = getResolvedApiKey ( )
561570 if ( apiKey ) {
562571 headers [ "Authorization" ] = apiKey
@@ -674,10 +683,8 @@ async function downloadAttachments(
674683 for ( const attachment of attachments ) {
675684 try {
676685 // Skip non-file URLs (e.g., external links)
677- // Linear uses uploads.linear.app for private and public.linear.app for public images
678- const isLinearUpload = attachment . url . includes ( "uploads.linear.app" ) ||
679- attachment . url . includes ( "public.linear.app" )
680- if ( ! isLinearUpload ) {
686+ const uploadHost = getLinearUploadHost ( attachment . url )
687+ if ( ! uploadHost ) {
681688 continue
682689 }
683690
@@ -694,8 +701,7 @@ async function downloadAttachments(
694701 }
695702
696703 const headers : Record < string , string > = { }
697- // Only add auth header for private uploads, not public URLs
698- if ( attachment . url . includes ( "uploads.linear.app" ) ) {
704+ if ( uploadHost === LINEAR_PRIVATE_UPLOAD_HOST ) {
699705 const apiKey = getResolvedApiKey ( )
700706 if ( apiKey ) {
701707 headers [ "Authorization" ] = apiKey
0 commit comments