Skip to content

Commit a80fbd2

Browse files
committed
fix(buffer): presign uploaded media for 7 days — Buffer fetches assets at publish time
1 parent c5ecd7a commit a80fbd2

5 files changed

Lines changed: 44 additions & 10 deletions

File tree

apps/docs/content/docs/en/integrations/buffer.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Create a post in Buffer for a channel — add it to the queue, share it immediat
3333
| `schedulingType` | string | No | How the post publishes: automatic \(Buffer publishes it, default\) or notification \(you get a mobile reminder\) |
3434
| `dueAt` | string | No | Publish time as an ISO 8601 timestamp \(required when mode is customScheduled\) |
3535
| `saveToDraft` | boolean | No | Save the post as a draft instead of scheduling it |
36-
| `media` | file | No | Image or video to attach — an uploaded file, a file reference from a previous block, or a publicly accessible URL |
36+
| `media` | file | No | Image or video to attach — an uploaded file, a file reference from a previous block, or a publicly accessible URL. Buffer downloads the media at publish time; uploaded files are shared via a link valid for 7 days, so use a public URL for posts scheduled further out |
3737
| `mediaType` | string | No | Force the attachment type when it cannot be detected from the file or URL: image or video \(default auto\) |
3838
| `mediaAltText` | string | No | Alt text for an attached image |
3939

@@ -83,7 +83,7 @@ Edit an existing Buffer post — update its text, schedule, or media. Attaching
8383
| `schedulingType` | string | No | How the post publishes: automatic \(Buffer publishes it, default\) or notification \(you get a mobile reminder\) |
8484
| `dueAt` | string | No | Publish time as an ISO 8601 timestamp \(required when mode is customScheduled\) |
8585
| `saveToDraft` | boolean | No | Save the post as a draft instead of scheduling it |
86-
| `media` | file | No | Image or video to attach — an uploaded file, a file reference from a previous block, or a publicly accessible URL. Replaces existing attachments |
86+
| `media` | file | No | Image or video to attach — an uploaded file, a file reference from a previous block, or a publicly accessible URL. Buffer downloads the media at publish time; uploaded files are shared via a link valid for 7 days, so use a public URL for posts scheduled further out. Replaces existing attachments |
8787
| `mediaType` | string | No | Force the attachment type when it cannot be detected from the file or URL: image or video \(default auto\) |
8888
| `mediaAltText` | string | No | Alt text for an attached image |
8989

apps/sim/app/api/tools/buffer/server-utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ import {
1818
const VIDEO_EXTENSIONS = ['.mp4', '.mov', '.m4v', '.webm', '.avi']
1919
const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp']
2020
const MEDIA_PROBE_TIMEOUT_MS = 5000
21+
/**
22+
* Buffer fetches asset URLs at publish time, which for queued or scheduled
23+
* posts can be days after createPost. Presign stored files for the S3 maximum
24+
* of 7 days so scheduled posts within that window can still be published.
25+
*/
26+
const MEDIA_PRESIGN_EXPIRY_SECONDS = 7 * 24 * 60 * 60
2127

2228
const CREATE_POST_MUTATION = `
2329
mutation CreatePost($input: CreatePostInput!) {
@@ -133,6 +139,7 @@ export async function resolveMediaAsset(
133139
userId,
134140
requestId,
135141
logger,
142+
presignExpirySeconds: MEDIA_PRESIGN_EXPIRY_SECONDS,
136143
})
137144
if (resolution.error || !resolution.fileUrl) {
138145
return {

apps/sim/lib/uploads/utils/file-utils.server.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ export interface ResolveFileInputOptions {
4949
userId: string
5050
requestId: string
5151
logger: Logger
52+
/**
53+
* Expiry for presigned URLs minted for stored files, in seconds.
54+
* Defaults to 5 minutes; raise it only when the external service fetches
55+
* the URL later than the current request (e.g. scheduled publishing).
56+
*/
57+
presignExpirySeconds?: number
5258
}
5359

5460
/**
@@ -62,7 +68,7 @@ export interface ResolveFileInputOptions {
6268
export async function resolveFileInputToUrl(
6369
options: ResolveFileInputOptions
6470
): Promise<FileResolutionResult> {
65-
const { file, filePath, userId, requestId, logger } = options
71+
const { file, filePath, userId, requestId, logger, presignExpirySeconds = 5 * 60 } = options
6672

6773
if (file) {
6874
let userFile: UserFile
@@ -81,7 +87,13 @@ export async function resolveFileInputToUrl(
8187

8288
// Handle internal URLs
8389
if (fileUrl && isInternalFileUrl(fileUrl)) {
84-
const resolution = await resolveInternalFileUrl(fileUrl, userId, requestId, logger)
90+
const resolution = await resolveInternalFileUrl(
91+
fileUrl,
92+
userId,
93+
requestId,
94+
logger,
95+
presignExpirySeconds
96+
)
8597
if (resolution.error) {
8698
return { error: resolution.error }
8799
}
@@ -102,7 +114,11 @@ export async function resolveFileInputToUrl(
102114
return { error: { status: 404, message: 'File not found' } }
103115
}
104116

105-
fileUrl = await StorageService.generatePresignedDownloadUrl(userFile.key, context, 5 * 60)
117+
fileUrl = await StorageService.generatePresignedDownloadUrl(
118+
userFile.key,
119+
context,
120+
presignExpirySeconds
121+
)
106122
}
107123

108124
return { fileUrl }
@@ -112,7 +128,13 @@ export async function resolveFileInputToUrl(
112128
let fileUrl = filePath
113129

114130
if (isInternalFileUrl(filePath)) {
115-
const resolution = await resolveInternalFileUrl(filePath, userId, requestId, logger)
131+
const resolution = await resolveInternalFileUrl(
132+
filePath,
133+
userId,
134+
requestId,
135+
logger,
136+
presignExpirySeconds
137+
)
116138
if (resolution.error) {
117139
return { error: resolution.error }
118140
}
@@ -227,7 +249,8 @@ export async function resolveInternalFileUrl(
227249
filePath: string,
228250
userId: string,
229251
requestId: string,
230-
logger: Logger
252+
logger: Logger,
253+
presignExpirySeconds = 5 * 60
231254
): Promise<{ fileUrl?: string; error?: { status: number; message: string } }> {
232255
if (!isInternalFileUrl(filePath)) {
233256
return { fileUrl: filePath }
@@ -247,7 +270,11 @@ export async function resolveInternalFileUrl(
247270
return { error: { status: 404, message: 'File not found' } }
248271
}
249272

250-
const fileUrl = await StorageService.generatePresignedDownloadUrl(storageKey, context, 5 * 60)
273+
const fileUrl = await StorageService.generatePresignedDownloadUrl(
274+
storageKey,
275+
context,
276+
presignExpirySeconds
277+
)
251278
logger.info(`[${requestId}] Generated presigned URL for ${context} file`)
252279
return { fileUrl }
253280
} catch (error) {

apps/sim/tools/buffer/create_post.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const bufferCreatePostTool: ToolConfig<BufferCreatePostParams, BufferPost
6262
required: false,
6363
visibility: 'user-or-llm',
6464
description:
65-
'Image or video to attach — an uploaded file, a file reference from a previous block, or a publicly accessible URL',
65+
'Image or video to attach — an uploaded file, a file reference from a previous block, or a publicly accessible URL. Buffer downloads the media at publish time; uploaded files are shared via a link valid for 7 days, so use a public URL for posts scheduled further out',
6666
},
6767
mediaType: {
6868
type: 'string',

apps/sim/tools/buffer/edit_post.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const bufferEditPostTool: ToolConfig<BufferEditPostParams, BufferPostResp
6262
required: false,
6363
visibility: 'user-or-llm',
6464
description:
65-
'Image or video to attach — an uploaded file, a file reference from a previous block, or a publicly accessible URL. Replaces existing attachments',
65+
'Image or video to attach — an uploaded file, a file reference from a previous block, or a publicly accessible URL. Buffer downloads the media at publish time; uploaded files are shared via a link valid for 7 days, so use a public URL for posts scheduled further out. Replaces existing attachments',
6666
},
6767
mediaType: {
6868
type: 'string',

0 commit comments

Comments
 (0)