Skip to content

Commit 6c7dd80

Browse files
committed
fix: preview url, and refactore file location and naming
1 parent 829ba32 commit 6c7dd80

File tree

13 files changed

+44
-61
lines changed

13 files changed

+44
-61
lines changed

cloudflare_workers/api/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { app as devices_priv } from '../../supabase/functions/_backend/private/d
88
import { app as events } from '../../supabase/functions/_backend/private/events.ts'
99
import { app as log_as } from '../../supabase/functions/_backend/private/log_as.ts'
1010
import { app as plans } from '../../supabase/functions/_backend/private/plans.ts'
11-
import { app as preview } from '../../supabase/functions/_backend/private/preview.ts'
1211
import { app as publicStats } from '../../supabase/functions/_backend/private/public_stats.ts'
1312
import { app as stats_priv } from '../../supabase/functions/_backend/private/stats.ts'
1413
import { app as storeTop } from '../../supabase/functions/_backend/private/store_top.ts'
@@ -78,7 +77,6 @@ appPrivate.route('/stripe_portal', stripe_portal)
7877
appPrivate.route('/delete_failed_version', deleted_failed_version)
7978
appPrivate.route('/create_device', create_device)
8079
appPrivate.route('/events', events)
81-
appPrivate.route('/preview', preview)
8280

8381
// Triggers
8482
const functionNameTriggers = 'triggers'

cloudflare_workers/files/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import { env } from 'node:process'
2+
import { app as files } from '../../supabase/functions/_backend/files/files.ts'
3+
import { app as preview } from '../../supabase/functions/_backend/files/preview.ts'
24
import { app as download_link } from '../../supabase/functions/_backend/private/download_link.ts'
3-
import { app as files } from '../../supabase/functions/_backend/private/files.ts'
45
import { app as upload_link } from '../../supabase/functions/_backend/private/upload_link.ts'
56
import { app as ok } from '../../supabase/functions/_backend/public/ok.ts'
67
import { createAllCatch, createHono } from '../../supabase/functions/_backend/utils/hono.ts'
78
import { version } from '../../supabase/functions/_backend/utils/version.ts'
89

9-
export { AttachmentUploadHandler, UploadHandler } from '../../supabase/functions/_backend/tus/uploadHandler.ts'
10+
export { AttachmentUploadHandler, UploadHandler } from '../../supabase/functions/_backend/files/uploadHandler.ts'
1011

1112
const functionName = 'files'
1213
const app = createHono(functionName, version, env.SENTRY_DSN)
1314

1415
// Files API
1516
app.route('/files', files)
1617
app.route('/ok', ok)
18+
app.route('/preview', preview)
1719

1820
// TODO: remove deprecated path when all users have been migrated
1921
app.route('/private/download_link', download_link)

src/components/BundlePreviewFrame.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ const currentDevice = computed(() => devices[selectedDevice.value])
7575
7676
// Build the preview URL with auth token
7777
const previewUrl = computed(() => {
78-
// Use Cloudflare Workers API for preview (supports streaming and proper file serving)
78+
// Use Cloudflare Workers Files endpoint for preview (has R2 bucket access for serving HTML files)
79+
// Note: Preview does NOT work on Supabase Edge Functions due to platform limitations
7980
// Pass token as query param since iframes can't send headers
8081
const tokenParam = accessToken.value ? `?token=${accessToken.value}` : ''
81-
return `${defaultApiHost}/private/preview/${props.appId}/${props.versionId}/${tokenParam}`
82+
return `${defaultApiHost}/files/preview/${props.appId}/${props.versionId}/${tokenParam}`
8283
})
8384
8485
// Build URL for QR code (includes fullscreen param)
File renamed without changes.

supabase/functions/_backend/private/files.ts renamed to supabase/functions/_backend/files/files.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import type { Database } from '../utils/supabase.types.ts'
44
import { getRuntimeKey } from 'hono/adapter'
55
import { HTTPException } from 'hono/http-exception'
66
import { Hono } from 'hono/tiny'
7+
import { app as download_link } from '../private/download_link.ts'
8+
import { app as upload_link } from '../private/upload_link.ts'
79
import { app as ok } from '../public/ok.ts'
8-
import { parseUploadMetadata } from '../tus/parse.ts'
9-
import { DEFAULT_RETRY_PARAMS, RetryBucket } from '../tus/retry.ts'
10-
import { ALLOWED_HEADERS, ALLOWED_METHODS, EXPOSED_HEADERS, MAX_UPLOAD_LENGTH_BYTES, toBase64, TUS_VERSION, X_CHECKSUM_SHA256 } from '../tus/util.ts'
1110
import { simpleError } from '../utils/hono.ts'
1211
import { middlewareKey } from '../utils/hono_middleware.ts'
1312
import { cloudlog } from '../utils/logging.ts'
@@ -16,9 +15,10 @@ import { getAppByAppIdPg, getUserIdFromApikey, hasAppRightApikeyPg } from '../ut
1615
import { createStatsBandwidth } from '../utils/stats.ts'
1716
import { supabaseAdmin } from '../utils/supabase.ts'
1817
import { backgroundTask } from '../utils/utils.ts'
19-
import { app as download_link } from './download_link.ts'
2018
import { app as files_config } from './files_config.ts'
21-
import { app as upload_link } from './upload_link.ts'
19+
import { parseUploadMetadata } from './parse.ts'
20+
import { DEFAULT_RETRY_PARAMS, RetryBucket } from './retry.ts'
21+
import { ALLOWED_HEADERS, ALLOWED_METHODS, EXPOSED_HEADERS, MAX_UPLOAD_LENGTH_BYTES, toBase64, TUS_VERSION, X_CHECKSUM_SHA256 } from './util.ts'
2222

2323
const DO_CALL_TIMEOUT = 1000 * 60 * 30 // 20 minutes
2424

supabase/functions/_backend/private/files_config.ts renamed to supabase/functions/_backend/files/files_config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { MiddlewareKeyVariables } from '../utils/hono.ts'
22
import { getRuntimeKey } from 'hono/adapter'
33
import { Hono } from 'hono/tiny'
4-
import { ALERT_UPLOAD_SIZE_BYTES, MAX_CHUNK_SIZE_BYTES, MAX_UPLOAD_LENGTH_BYTES } from '../tus/util.ts'
4+
import { ALERT_UPLOAD_SIZE_BYTES, MAX_CHUNK_SIZE_BYTES, MAX_UPLOAD_LENGTH_BYTES } from './util.ts'
55
import { useCors } from '../utils/hono.ts'
66

77
export const app = new Hono<MiddlewareKeyVariables>()
File renamed without changes.

supabase/functions/_backend/private/preview.ts renamed to supabase/functions/_backend/files/preview.ts

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import type { Context } from 'hono'
22
import type { MiddlewareKeyVariables } from '../utils/hono.ts'
33
import { getRuntimeKey } from 'hono/adapter'
44
import { Hono } from 'hono/tiny'
5-
import { DEFAULT_RETRY_PARAMS, RetryBucket } from '../tus/retry.ts'
65
import { simpleError, useCors } from '../utils/hono.ts'
76
import { cloudlog } from '../utils/logging.ts'
8-
import { s3 } from '../utils/s3.ts'
97
import { hasAppRight, supabaseAdmin } from '../utils/supabase.ts'
8+
import { DEFAULT_RETRY_PARAMS, RetryBucket } from './retry.ts'
109

1110
// MIME type mapping for common file extensions
1211
const MIME_TYPES: Record<string, string> = {
@@ -156,54 +155,36 @@ async function handlePreview(c: Context<MiddlewareKeyVariables>) {
156155
return simpleError('file_not_found', 'File not found in bundle', { filePath })
157156
}
158157

159-
// Serve the file - use R2 bucket directly in Cloudflare Workers, or fetch via presigned URL in Supabase
160-
try {
161-
if (getRuntimeKey() === 'workerd') {
162-
// Cloudflare Workers - use R2 bucket directly
163-
const bucket = c.env.ATTACHMENT_BUCKET
164-
if (!bucket) {
165-
cloudlog({ requestId: c.get('requestId'), message: 'preview bucket is null' })
166-
return simpleError('bucket_not_configured', 'Storage bucket not configured')
167-
}
168-
169-
const object = await new RetryBucket(bucket, DEFAULT_RETRY_PARAMS).get(manifestEntry.s3_path)
170-
if (!object) {
171-
cloudlog({ requestId: c.get('requestId'), message: 'file not found in R2', s3_path: manifestEntry.s3_path })
172-
return simpleError('file_not_found', 'File not found in storage', { filePath })
173-
}
158+
// Preview only works on Cloudflare Workers where the R2 bucket is available.
159+
// Supabase Edge Functions cannot serve HTML files properly due to platform limitations.
160+
if (getRuntimeKey() !== 'workerd') {
161+
cloudlog({ requestId: c.get('requestId'), message: 'preview not supported on Supabase Edge Functions' })
162+
return simpleError('preview_not_supported', 'Preview is not supported on Supabase Edge Functions. This feature requires Cloudflare Workers with R2 bucket access.')
163+
}
174164

175-
// Use our own MIME type detection - R2 rewrites text/html to text/plain without custom domains
176-
const contentType = getContentType(filePath)
177-
const headers = new Headers()
178-
headers.set('Content-Type', contentType)
179-
headers.set('etag', object.httpEtag)
180-
headers.set('Cache-Control', 'private, max-age=3600')
181-
headers.set('X-Content-Type-Options', 'nosniff')
165+
const bucket = c.env.ATTACHMENT_BUCKET
166+
if (!bucket) {
167+
cloudlog({ requestId: c.get('requestId'), message: 'preview bucket is null' })
168+
return simpleError('bucket_not_configured', 'Storage bucket not configured')
169+
}
182170

183-
cloudlog({ requestId: c.get('requestId'), message: 'serving preview file from R2', filePath, contentType })
184-
return new Response(object.body, { headers })
171+
try {
172+
const object = await new RetryBucket(bucket, DEFAULT_RETRY_PARAMS).get(manifestEntry.s3_path)
173+
if (!object) {
174+
cloudlog({ requestId: c.get('requestId'), message: 'file not found in R2', s3_path: manifestEntry.s3_path })
175+
return simpleError('file_not_found', 'File not found in storage', { filePath })
185176
}
186-
else {
187-
// Supabase Edge Functions - fetch via presigned URL
188-
const response = await s3.getObject(c, manifestEntry.s3_path)
189-
if (!response) {
190-
cloudlog({ requestId: c.get('requestId'), message: 'failed to fetch file from S3', s3_path: manifestEntry.s3_path })
191-
return simpleError('file_fetch_failed', 'Failed to fetch file')
192-
}
193177

194-
// Use our MIME type detection based on file extension
195-
const contentType = getContentType(filePath)
196-
cloudlog({ requestId: c.get('requestId'), message: 'serving preview file via S3', filePath, contentType })
197-
198-
return new Response(response.body, {
199-
status: 200,
200-
headers: {
201-
'Content-Type': contentType,
202-
'Cache-Control': 'private, max-age=3600',
203-
'X-Content-Type-Options': 'nosniff',
204-
},
205-
})
206-
}
178+
// Use our own MIME type detection - R2 rewrites text/html to text/plain without custom domains
179+
const contentType = getContentType(filePath)
180+
const headers = new Headers()
181+
headers.set('Content-Type', contentType)
182+
headers.set('etag', object.httpEtag)
183+
headers.set('Cache-Control', 'private, max-age=3600')
184+
headers.set('X-Content-Type-Options', 'nosniff')
185+
186+
cloudlog({ requestId: c.get('requestId'), message: 'serving preview file from R2', filePath, contentType })
187+
return new Response(object.body, { headers })
207188
}
208189
catch (error) {
209190
cloudlog({ requestId: c.get('requestId'), message: 'failed to serve preview file', error, s3_path: manifestEntry.s3_path })
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)