@remotion/lambda-client 4.0.261
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-make.log +6 -0
- package/LICENSE.md +49 -0
- package/README.md +5 -0
- package/bundle.ts +20 -0
- package/dist/app-router-webhook.d.ts +10 -0
- package/dist/app-router-webhook.js +43 -0
- package/dist/apply-lifecycle.d.ts +8 -0
- package/dist/apply-lifecycle.js +18 -0
- package/dist/aws-clients.d.ts +11 -0
- package/dist/aws-clients.js +51 -0
- package/dist/aws-provider.d.ts +23 -0
- package/dist/aws-provider.js +94 -0
- package/dist/bucket-exists.d.ts +3 -0
- package/dist/bucket-exists.js +26 -0
- package/dist/call-lambda-async.d.ts +2 -0
- package/dist/call-lambda-async.js +20 -0
- package/dist/call-lambda-streaming.d.ts +6 -0
- package/dist/call-lambda-streaming.js +125 -0
- package/dist/call-lambda-sync.d.ts +2 -0
- package/dist/call-lambda-sync.js +30 -0
- package/dist/check-credentials.d.ts +1 -0
- package/dist/check-credentials.js +39 -0
- package/dist/clean-items.d.ts +16 -0
- package/dist/clean-items.js +25 -0
- package/dist/constants.d.ts +25 -0
- package/dist/constants.js +26 -0
- package/dist/content-disposition-header.d.ts +2 -0
- package/dist/content-disposition-header.js +50 -0
- package/dist/convert-to-serve-url.d.ts +6 -0
- package/dist/convert-to-serve-url.js +14 -0
- package/dist/create-bucket.d.ts +3 -0
- package/dist/create-bucket.js +45 -0
- package/dist/delete-file.d.ts +3 -0
- package/dist/delete-file.js +12 -0
- package/dist/delete-function.d.ts +4 -0
- package/dist/delete-function.js +15 -0
- package/dist/delete-render.d.ts +20 -0
- package/dist/delete-render.js +84 -0
- package/dist/encode-aws-url-params.d.ts +1 -0
- package/dist/encode-aws-url-params.js +7 -0
- package/dist/esm/constants.mjs +45 -0
- package/dist/esm/index.mjs +6516 -0
- package/dist/esm/regions.mjs +50 -0
- package/dist/estimate-price.d.ts +17 -0
- package/dist/estimate-price.js +45 -0
- package/dist/express-webhook.d.ts +3 -0
- package/dist/express-webhook.js +46 -0
- package/dist/get-account-id.d.ts +3 -0
- package/dist/get-account-id.js +15 -0
- package/dist/get-aws-client.d.ts +30 -0
- package/dist/get-aws-client.js +56 -0
- package/dist/get-aws-urls.d.ts +25 -0
- package/dist/get-aws-urls.js +31 -0
- package/dist/get-buckets.d.ts +9 -0
- package/dist/get-buckets.js +62 -0
- package/dist/get-compositions-on-lambda.d.ts +18 -0
- package/dist/get-compositions-on-lambda.js +59 -0
- package/dist/get-credentials.d.ts +9 -0
- package/dist/get-credentials.js +55 -0
- package/dist/get-env-variable.d.ts +1 -0
- package/dist/get-env-variable.js +15 -0
- package/dist/get-function-name.d.ts +8 -0
- package/dist/get-function-name.js +17 -0
- package/dist/get-function-version.d.ts +7 -0
- package/dist/get-function-version.js +32 -0
- package/dist/get-functions.d.ts +8 -0
- package/dist/get-functions.js +77 -0
- package/dist/get-output-url-from-metadata.d.ts +3 -0
- package/dist/get-output-url-from-metadata.js +18 -0
- package/dist/get-render-progress.d.ts +15 -0
- package/dist/get-render-progress.js +45 -0
- package/dist/get-s3-client.d.ts +9 -0
- package/dist/get-s3-client.js +14 -0
- package/dist/get-service-client.d.ts +23 -0
- package/dist/get-service-client.js +120 -0
- package/dist/get-sites.d.ts +29 -0
- package/dist/get-sites.js +79 -0
- package/dist/head-file.d.ts +3 -0
- package/dist/head-file.js +17 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +104 -0
- package/dist/is-cli.d.ts +2 -0
- package/dist/is-cli.js +10 -0
- package/dist/is-flaky-error.d.ts +1 -0
- package/dist/is-flaky-error.js +77 -0
- package/dist/is-in-lambda.d.ts +1 -0
- package/dist/is-in-lambda.js +9 -0
- package/dist/is-likely-to-have-aws-profile.d.ts +1 -0
- package/dist/is-likely-to-have-aws-profile.js +50 -0
- package/dist/lambda-version-string.d.ts +1 -0
- package/dist/lambda-version-string.js +7 -0
- package/dist/lifecycle-rules.d.ts +10 -0
- package/dist/lifecycle-rules.js +61 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.js +24 -0
- package/dist/list-objects.d.ts +3 -0
- package/dist/list-objects.js +67 -0
- package/dist/make-lambda-payload.d.ts +54 -0
- package/dist/make-lambda-payload.js +148 -0
- package/dist/make-s3-url.d.ts +6 -0
- package/dist/make-s3-url.js +7 -0
- package/dist/p-limit.d.ts +1 -0
- package/dist/p-limit.js +57 -0
- package/dist/pages-router-webhook.d.ts +5 -0
- package/dist/pages-router-webhook.js +48 -0
- package/dist/parse-function-name.d.ts +8 -0
- package/dist/parse-function-name.js +17 -0
- package/dist/presign-url.d.ts +14 -0
- package/dist/presign-url.js +64 -0
- package/dist/price-per-1s.d.ts +37 -0
- package/dist/price-per-1s.js +822 -0
- package/dist/random-hash.d.ts +1 -0
- package/dist/random-hash.js +13 -0
- package/dist/read-file.d.ts +9 -0
- package/dist/read-file.js +18 -0
- package/dist/regions.d.ts +3 -0
- package/dist/regions.js +48 -0
- package/dist/render-media-on-lambda.d.ts +61 -0
- package/dist/render-media-on-lambda.js +127 -0
- package/dist/render-still-on-lambda.d.ts +53 -0
- package/dist/render-still-on-lambda.js +118 -0
- package/dist/runtime-preference.d.ts +2 -0
- package/dist/runtime-preference.js +8 -0
- package/dist/speculate-function-name.d.ts +6 -0
- package/dist/speculate-function-name.js +20 -0
- package/dist/test/encode-aws-url.test.d.ts +1 -0
- package/dist/test/encode-aws-url.test.js +8 -0
- package/dist/test/price-calculation.test.d.ts +1 -0
- package/dist/test/price-calculation.test.js +61 -0
- package/dist/test/pricing.test.d.ts +1 -0
- package/dist/test/pricing.test.js +27 -0
- package/dist/test/validate-disk-size-in-mb.test.d.ts +1 -0
- package/dist/test/validate-disk-size-in-mb.test.js +14 -0
- package/dist/validate-aws-region.d.ts +2 -0
- package/dist/validate-aws-region.js +9 -0
- package/dist/validate-bucketname.d.ts +4 -0
- package/dist/validate-bucketname.js +15 -0
- package/dist/validate-disk-size-in-mb.d.ts +1 -0
- package/dist/validate-disk-size-in-mb.js +23 -0
- package/dist/validate-lambda-codec.d.ts +2 -0
- package/dist/validate-lambda-codec.js +21 -0
- package/dist/validate-memory-size.d.ts +1 -0
- package/dist/validate-memory-size.js +22 -0
- package/dist/validate-presign-expiration.d.ts +1 -0
- package/dist/validate-presign-expiration.js +29 -0
- package/dist/validate-serveurl.d.ts +1 -0
- package/dist/validate-serveurl.js +9 -0
- package/dist/validate-webhook-signature.d.ts +5 -0
- package/dist/validate-webhook-signature.js +28 -0
- package/dist/write-file.d.ts +5 -0
- package/dist/write-file.js +56 -0
- package/eslint.config.mjs +5 -0
- package/package.json +71 -0
- package/src/app-router-webhook.ts +64 -0
- package/src/apply-lifecycle.ts +30 -0
- package/src/aws-clients.ts +60 -0
- package/src/aws-provider.ts +135 -0
- package/src/bucket-exists.ts +28 -0
- package/src/call-lambda-async.ts +39 -0
- package/src/call-lambda-streaming.ts +219 -0
- package/src/call-lambda-sync.ts +55 -0
- package/src/check-credentials.ts +51 -0
- package/src/clean-items.ts +47 -0
- package/src/constants.ts +38 -0
- package/src/content-disposition-header.ts +64 -0
- package/src/convert-to-serve-url.ts +24 -0
- package/src/create-bucket.ts +67 -0
- package/src/delete-file.ts +30 -0
- package/src/delete-function.ts +24 -0
- package/src/delete-render.ts +107 -0
- package/src/encode-aws-url-params.ts +3 -0
- package/src/estimate-price.ts +95 -0
- package/src/express-webhook.ts +50 -0
- package/src/get-account-id.ts +22 -0
- package/src/get-aws-client.ts +59 -0
- package/src/get-aws-urls.ts +85 -0
- package/src/get-buckets.ts +81 -0
- package/src/get-compositions-on-lambda.ts +104 -0
- package/src/get-credentials.ts +81 -0
- package/src/get-env-variable.ts +15 -0
- package/src/get-function-name.ts +24 -0
- package/src/get-function-version.ts +43 -0
- package/src/get-functions.ts +103 -0
- package/src/get-output-url-from-metadata.ts +23 -0
- package/src/get-render-progress.ts +62 -0
- package/src/get-s3-client.ts +22 -0
- package/src/get-service-client.ts +178 -0
- package/src/get-sites.ts +128 -0
- package/src/head-file.ts +28 -0
- package/src/index.ts +147 -0
- package/src/is-cli.ts +7 -0
- package/src/is-flaky-error.ts +101 -0
- package/src/is-in-lambda.ts +5 -0
- package/src/is-likely-to-have-aws-profile.ts +55 -0
- package/src/lambda-version-string.ts +5 -0
- package/src/lifecycle-rules.ts +104 -0
- package/src/lifecycle.ts +44 -0
- package/src/list-objects.ts +83 -0
- package/src/make-lambda-payload.ts +317 -0
- package/src/make-s3-url.ts +13 -0
- package/src/p-limit.ts +75 -0
- package/src/pages-router-webhook.ts +58 -0
- package/src/parse-function-name.ts +24 -0
- package/src/presign-url.ts +110 -0
- package/src/price-per-1s.ts +863 -0
- package/src/random-hash.ts +10 -0
- package/src/read-file.ts +31 -0
- package/src/regions.ts +48 -0
- package/src/render-media-on-lambda.ts +216 -0
- package/src/render-still-on-lambda.ts +195 -0
- package/src/runtime-preference.ts +7 -0
- package/src/speculate-function-name.ts +27 -0
- package/src/test/encode-aws-url.test.ts +7 -0
- package/src/test/price-calculation.test.ts +61 -0
- package/src/test/pricing.test.ts +32 -0
- package/src/test/validate-disk-size-in-mb.test.ts +15 -0
- package/src/validate-aws-region.ts +14 -0
- package/src/validate-bucketname.ts +24 -0
- package/src/validate-disk-size-in-mb.ts +37 -0
- package/src/validate-lambda-codec.ts +28 -0
- package/src/validate-memory-size.ts +31 -0
- package/src/validate-presign-expiration.ts +46 -0
- package/src/validate-serveurl.ts +9 -0
- package/src/validate-webhook-signature.ts +42 -0
- package/src/write-file.ts +74 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {InvokeCommand} from '@aws-sdk/client-lambda';
|
|
2
|
+
import type {
|
|
3
|
+
CallFunctionOptions,
|
|
4
|
+
CloudProvider,
|
|
5
|
+
ServerlessRoutines,
|
|
6
|
+
} from '@remotion/serverless-client';
|
|
7
|
+
import {getLambdaClient} from './aws-clients';
|
|
8
|
+
import type {AwsRegion} from './regions';
|
|
9
|
+
|
|
10
|
+
export const callFunctionAsyncImplementation = async <
|
|
11
|
+
T extends ServerlessRoutines,
|
|
12
|
+
Provider extends CloudProvider,
|
|
13
|
+
>({
|
|
14
|
+
functionName,
|
|
15
|
+
payload,
|
|
16
|
+
region,
|
|
17
|
+
timeoutInTest,
|
|
18
|
+
}: CallFunctionOptions<T, Provider>): Promise<void> => {
|
|
19
|
+
const stringifiedPayload = JSON.stringify(payload);
|
|
20
|
+
if (stringifiedPayload.length > 256 * 1024) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Payload is too big: ${stringifiedPayload.length} bytes. Maximum size is 256 KB. This should not happen, please report this to the Remotion team. Payload: ${stringifiedPayload}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = await getLambdaClient(region as AwsRegion, timeoutInTest).send(
|
|
27
|
+
new InvokeCommand({
|
|
28
|
+
FunctionName: functionName,
|
|
29
|
+
Payload: stringifiedPayload,
|
|
30
|
+
InvocationType: 'Event',
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (result.FunctionError) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Lambda function returned error: ${result.FunctionError} ${result.LogResult}`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import type {InvokeWithResponseStreamCommandOutput} from '@aws-sdk/client-lambda';
|
|
3
|
+
import {
|
|
4
|
+
InvokeWithResponseStreamCommand,
|
|
5
|
+
type InvokeWithResponseStreamResponseEvent,
|
|
6
|
+
} from '@aws-sdk/client-lambda';
|
|
7
|
+
import type {
|
|
8
|
+
CallFunctionOptions,
|
|
9
|
+
CloudProvider,
|
|
10
|
+
MessageTypeId,
|
|
11
|
+
OnMessage,
|
|
12
|
+
ServerlessRoutines,
|
|
13
|
+
StreamingMessage,
|
|
14
|
+
} from '@remotion/serverless-client';
|
|
15
|
+
import {
|
|
16
|
+
formatMap,
|
|
17
|
+
makeStreamer,
|
|
18
|
+
messageTypeIdToMessageType,
|
|
19
|
+
} from '@remotion/serverless-client';
|
|
20
|
+
import {getLambdaClient} from './aws-clients';
|
|
21
|
+
import type {AwsRegion} from './regions';
|
|
22
|
+
|
|
23
|
+
const STREAM_STALL_TIMEOUT = 30000;
|
|
24
|
+
const LAMBDA_STREAM_STALL = `AWS did not invoke Lambda in ${STREAM_STALL_TIMEOUT}ms`;
|
|
25
|
+
|
|
26
|
+
export const parseJsonOrThrowSource = (data: Uint8Array, type: string) => {
|
|
27
|
+
const asString = new TextDecoder('utf-8').decode(data);
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(asString);
|
|
30
|
+
} catch {
|
|
31
|
+
throw new Error(`Invalid JSON (${type}): ${asString}`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const invokeStreamOrTimeout = async <Provider extends CloudProvider>({
|
|
36
|
+
region,
|
|
37
|
+
timeoutInTest,
|
|
38
|
+
functionName,
|
|
39
|
+
type,
|
|
40
|
+
payload,
|
|
41
|
+
}: {
|
|
42
|
+
region: Provider['region'];
|
|
43
|
+
timeoutInTest: number;
|
|
44
|
+
functionName: string;
|
|
45
|
+
type: string;
|
|
46
|
+
payload: Record<string, unknown>;
|
|
47
|
+
}) => {
|
|
48
|
+
const resProm = getLambdaClient(region as AwsRegion, timeoutInTest).send(
|
|
49
|
+
new InvokeWithResponseStreamCommand({
|
|
50
|
+
FunctionName: functionName,
|
|
51
|
+
Payload: JSON.stringify({type, ...payload}),
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
let cleanup = () => undefined;
|
|
56
|
+
|
|
57
|
+
const timeout = new Promise<InvokeWithResponseStreamCommandOutput>(
|
|
58
|
+
(_resolve, reject) => {
|
|
59
|
+
const int = setTimeout(() => {
|
|
60
|
+
reject(new Error(LAMBDA_STREAM_STALL));
|
|
61
|
+
}, STREAM_STALL_TIMEOUT);
|
|
62
|
+
cleanup = () => {
|
|
63
|
+
clearTimeout(int);
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const res = await Promise.race([resProm, timeout]);
|
|
69
|
+
|
|
70
|
+
cleanup();
|
|
71
|
+
|
|
72
|
+
return res;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const INVALID_JSON_MESSAGE = 'Cannot parse Lambda response as JSON';
|
|
76
|
+
|
|
77
|
+
const callLambdaWithStreamingWithoutRetry = async <
|
|
78
|
+
T extends ServerlessRoutines,
|
|
79
|
+
Provider extends CloudProvider,
|
|
80
|
+
>({
|
|
81
|
+
functionName,
|
|
82
|
+
type,
|
|
83
|
+
payload,
|
|
84
|
+
region,
|
|
85
|
+
timeoutInTest,
|
|
86
|
+
receivedStreamingPayload,
|
|
87
|
+
}: CallFunctionOptions<T, Provider> & {
|
|
88
|
+
receivedStreamingPayload: OnMessage<Provider>;
|
|
89
|
+
}): Promise<void> => {
|
|
90
|
+
const res = await invokeStreamOrTimeout({
|
|
91
|
+
functionName,
|
|
92
|
+
payload,
|
|
93
|
+
region,
|
|
94
|
+
timeoutInTest,
|
|
95
|
+
type,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const {onData, clear} = makeStreamer((status, messageTypeId, data) => {
|
|
99
|
+
const messageType = messageTypeIdToMessageType(
|
|
100
|
+
messageTypeId as MessageTypeId,
|
|
101
|
+
);
|
|
102
|
+
const innerPayload =
|
|
103
|
+
formatMap[messageType] === 'json'
|
|
104
|
+
? parseJsonOrThrowSource(data, messageType)
|
|
105
|
+
: data;
|
|
106
|
+
|
|
107
|
+
const message: StreamingMessage<Provider> = {
|
|
108
|
+
successType: status,
|
|
109
|
+
message: {
|
|
110
|
+
type: messageType,
|
|
111
|
+
payload: innerPayload,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
receivedStreamingPayload(message);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const dumpBuffers = () => {
|
|
119
|
+
clear();
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// @ts-expect-error - We are adding a listener to a global variable
|
|
123
|
+
if (globalThis._dumpUnreleasedBuffers) {
|
|
124
|
+
// @ts-expect-error - We are adding a listener to a global variable
|
|
125
|
+
(globalThis._dumpUnreleasedBuffers as EventEmitter).addListener(
|
|
126
|
+
'dump-unreleased-buffers',
|
|
127
|
+
dumpBuffers,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const events =
|
|
132
|
+
res.EventStream as AsyncIterable<InvokeWithResponseStreamResponseEvent>;
|
|
133
|
+
|
|
134
|
+
for await (const event of events) {
|
|
135
|
+
// There are two types of events you can get on a stream.
|
|
136
|
+
|
|
137
|
+
// `PayloadChunk`: These contain the actual raw bytes of the chunk
|
|
138
|
+
// It has a single property: `Payload`
|
|
139
|
+
if (event.PayloadChunk && event.PayloadChunk.Payload) {
|
|
140
|
+
onData(event.PayloadChunk.Payload);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (event.InvokeComplete) {
|
|
144
|
+
if (event.InvokeComplete.ErrorCode) {
|
|
145
|
+
const logs = `https://${region}.console.aws.amazon.com/cloudwatch/home?region=${region}#logsV2:logs-insights$3FqueryDetail$3D~(end~0~start~-3600~timeType~'RELATIVE~unit~'seconds~editorString~'fields*20*40timestamp*2c*20*40requestId*2c*20*40message*0a*7c*20filter*20*40requestId*20like*20*${res.$metadata.requestId}*22*0a*7c*20sort*20*40timestamp*20asc~source~(~'*2faws*2flambda*2f${functionName}))`;
|
|
146
|
+
if (event.InvokeComplete.ErrorCode === 'Unhandled') {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Lambda function ${functionName} failed with an unhandled error: ${
|
|
149
|
+
event.InvokeComplete.ErrorDetails as string
|
|
150
|
+
} See ${logs} to see the logs of this invocation.`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Lambda function ${functionName} failed with error code ${event.InvokeComplete.ErrorCode}: ${event.InvokeComplete.ErrorDetails}. See ${logs} to see the logs of this invocation.`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Don't put a `break` statement here, as it will cause the socket to not properly exit.
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// @ts-expect-error - We are adding a listener to a global variable
|
|
164
|
+
if (globalThis._dumpUnreleasedBuffers) {
|
|
165
|
+
// @ts-expect-error - We are adding a listener to a global variable
|
|
166
|
+
(globalThis._dumpUnreleasedBuffers as EventEmitter).removeListener(
|
|
167
|
+
'dump-unreleased-buffers',
|
|
168
|
+
dumpBuffers,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
clear();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const callFunctionWithStreamingImplementation = async <
|
|
176
|
+
Provider extends CloudProvider,
|
|
177
|
+
T extends ServerlessRoutines,
|
|
178
|
+
>(
|
|
179
|
+
options: CallFunctionOptions<T, Provider> & {
|
|
180
|
+
receivedStreamingPayload: OnMessage<Provider>;
|
|
181
|
+
retriesRemaining: number;
|
|
182
|
+
},
|
|
183
|
+
): Promise<void> => {
|
|
184
|
+
// As of August 2023, Lambda streaming sometimes misses parts of the JSON response.
|
|
185
|
+
// Handling this for now by applying a retry mechanism.
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// Do not remove this await
|
|
189
|
+
await callLambdaWithStreamingWithoutRetry<T, Provider>(options);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if ((err as Error).stack?.includes('TooManyRequestsException')) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`AWS Concurrency limit reached (Original Error: ${(err as Error).message}). See https://www.remotion.dev/docs/lambda/troubleshooting/rate-limit for tips to fix this.`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
!(err as Error).message.includes(INVALID_JSON_MESSAGE) &&
|
|
199
|
+
!(err as Error).message.includes(LAMBDA_STREAM_STALL) &&
|
|
200
|
+
// https://discord.com/channels/809501355504959528/1332166561242288220/1332166561242288220
|
|
201
|
+
!(err as Error).message.includes('Runtime.TruncatedResponse') &&
|
|
202
|
+
!(err as Error).message.includes('aborted')
|
|
203
|
+
) {
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.error('Retries remaining:', options.retriesRemaining);
|
|
208
|
+
if (options.retriesRemaining === 0) {
|
|
209
|
+
console.error('Throwing error:');
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.error(err);
|
|
214
|
+
return callFunctionWithStreamingImplementation({
|
|
215
|
+
...options,
|
|
216
|
+
retriesRemaining: options.retriesRemaining - 1,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {InvokeCommand} from '@aws-sdk/client-lambda';
|
|
2
|
+
import type {
|
|
3
|
+
CallFunctionOptions,
|
|
4
|
+
CloudProvider,
|
|
5
|
+
OrError,
|
|
6
|
+
ServerlessReturnValues,
|
|
7
|
+
ServerlessRoutines,
|
|
8
|
+
} from '@remotion/serverless-client';
|
|
9
|
+
import {getLambdaClient} from './aws-clients';
|
|
10
|
+
import type {AwsRegion} from './regions';
|
|
11
|
+
|
|
12
|
+
const callLambdaSyncWithoutRetry = async <
|
|
13
|
+
T extends ServerlessRoutines,
|
|
14
|
+
Provider extends CloudProvider,
|
|
15
|
+
>({
|
|
16
|
+
functionName,
|
|
17
|
+
payload,
|
|
18
|
+
region,
|
|
19
|
+
timeoutInTest,
|
|
20
|
+
}: CallFunctionOptions<T, Provider>): Promise<
|
|
21
|
+
OrError<ServerlessReturnValues<Provider>[T]>
|
|
22
|
+
> => {
|
|
23
|
+
const Payload = JSON.stringify(payload);
|
|
24
|
+
const res = await getLambdaClient(region as AwsRegion, timeoutInTest).send(
|
|
25
|
+
new InvokeCommand({
|
|
26
|
+
FunctionName: functionName,
|
|
27
|
+
Payload,
|
|
28
|
+
InvocationType: 'RequestResponse',
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const decoded = new TextDecoder('utf-8').decode(res.Payload);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(decoded) as OrError<ServerlessReturnValues<Provider>[T]>;
|
|
36
|
+
} catch {
|
|
37
|
+
throw new Error(`Invalid JSON: ${JSON.stringify(decoded)}`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const callFunctionSyncImplementation = async <
|
|
42
|
+
Provider extends CloudProvider,
|
|
43
|
+
T extends ServerlessRoutines,
|
|
44
|
+
>(
|
|
45
|
+
options: CallFunctionOptions<T, Provider>,
|
|
46
|
+
): Promise<ServerlessReturnValues<Provider>[T]> => {
|
|
47
|
+
const res = await callLambdaSyncWithoutRetry<T, Provider>(options);
|
|
48
|
+
if (res.type === 'error') {
|
|
49
|
+
const err = new Error(res.message);
|
|
50
|
+
err.stack = res.stack;
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return res;
|
|
55
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {DOCS_URL, truthy} from '@remotion/serverless-client';
|
|
2
|
+
import {getEnvVariable} from './get-env-variable';
|
|
3
|
+
import {getIsCli} from './is-cli';
|
|
4
|
+
import {isLikelyToHaveAwsProfile} from './is-likely-to-have-aws-profile';
|
|
5
|
+
|
|
6
|
+
const messageForVariable = (variable: string) => {
|
|
7
|
+
return [
|
|
8
|
+
`You have tried to call a Remotion Lambda function, but have not set the environment variable ${variable}.`,
|
|
9
|
+
getIsCli()
|
|
10
|
+
? null
|
|
11
|
+
: `- Environment variables from a '.env' file are not automatically read if you are calling the Node.JS APIs, in that case you need to load the file yourself or set the environment variables manually.`,
|
|
12
|
+
`- Please refer to the Remotion Lambda docs (${DOCS_URL}/docs/lambda/setup) to see how to generate the credentials for your AWS account and then set the environment variables.`,
|
|
13
|
+
`- For more reasons see the troubleshooting page: ${DOCS_URL}/docs/lambda/troubleshooting/permissions`,
|
|
14
|
+
]
|
|
15
|
+
.filter(truthy)
|
|
16
|
+
.join('\n');
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const checkCredentials = () => {
|
|
20
|
+
if (getEnvVariable('REMOTION_SKIP_AWS_CREDENTIALS_CHECK')) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (getEnvVariable('REMOTION_AWS_PROFILE') || getEnvVariable('AWS_PROFILE')) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isLikelyToHaveAwsProfile()) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
!getEnvVariable('AWS_ACCESS_KEY_ID') &&
|
|
34
|
+
!getEnvVariable('REMOTION_AWS_ACCESS_KEY_ID')
|
|
35
|
+
) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
messageForVariable('AWS_ACCESS_KEY_ID or REMOTION_AWS_ACCESS_KEY_ID'),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
!getEnvVariable('AWS_SECRET_ACCESS_KEY') &&
|
|
43
|
+
!getEnvVariable('REMOTION_AWS_SECRET_ACCESS_KEY')
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
messageForVariable(
|
|
47
|
+
'AWS_SECRET_ACCESS_KEY or REMOTION_AWS_SECRET_ACCESS_KEY',
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CloudProvider,
|
|
3
|
+
ProviderSpecifics,
|
|
4
|
+
} from '@remotion/serverless-client';
|
|
5
|
+
import {pLimit} from './p-limit';
|
|
6
|
+
|
|
7
|
+
const limit = pLimit(10);
|
|
8
|
+
|
|
9
|
+
export const cleanItems = <Provider extends CloudProvider>({
|
|
10
|
+
bucket,
|
|
11
|
+
onAfterItemDeleted,
|
|
12
|
+
onBeforeItemDeleted,
|
|
13
|
+
region,
|
|
14
|
+
list,
|
|
15
|
+
providerSpecifics,
|
|
16
|
+
forcePathStyle,
|
|
17
|
+
}: {
|
|
18
|
+
bucket: string;
|
|
19
|
+
region: Provider['region'];
|
|
20
|
+
list: string[];
|
|
21
|
+
onBeforeItemDeleted: (data: {bucketName: string; itemName: string}) => void;
|
|
22
|
+
onAfterItemDeleted: (data: {bucketName: string; itemName: string}) => void;
|
|
23
|
+
providerSpecifics: ProviderSpecifics<Provider>;
|
|
24
|
+
forcePathStyle: boolean;
|
|
25
|
+
}) => {
|
|
26
|
+
return Promise.all(
|
|
27
|
+
list.map((object) =>
|
|
28
|
+
limit(async () => {
|
|
29
|
+
onBeforeItemDeleted({
|
|
30
|
+
bucketName: bucket,
|
|
31
|
+
itemName: object,
|
|
32
|
+
});
|
|
33
|
+
await providerSpecifics.deleteFile({
|
|
34
|
+
bucketName: bucket,
|
|
35
|
+
key: object,
|
|
36
|
+
region,
|
|
37
|
+
customCredentials: null,
|
|
38
|
+
forcePathStyle,
|
|
39
|
+
});
|
|
40
|
+
onAfterItemDeleted({
|
|
41
|
+
bucketName: bucket,
|
|
42
|
+
itemName: object,
|
|
43
|
+
});
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {GenericRenderProgress, Privacy} from '@remotion/serverless-client';
|
|
2
|
+
import type {AwsProvider} from './aws-provider';
|
|
3
|
+
import type {AwsRegion} from './regions';
|
|
4
|
+
|
|
5
|
+
export const MIN_MEMORY = 512;
|
|
6
|
+
export const MAX_MEMORY = 10240;
|
|
7
|
+
export const DEFAULT_MEMORY_SIZE = 2048;
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_TIMEOUT = 120;
|
|
10
|
+
export const MIN_TIMEOUT = 15;
|
|
11
|
+
export const MAX_TIMEOUT = 900;
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_FRAMES_PER_LAMBDA = 20;
|
|
14
|
+
|
|
15
|
+
export const BINARY_NAME = 'remotion lambda';
|
|
16
|
+
export const DEFAULT_REGION: AwsRegion = 'us-east-1';
|
|
17
|
+
export const DEFAULT_MAX_RETRIES = 1;
|
|
18
|
+
|
|
19
|
+
export const MAX_EPHEMERAL_STORAGE_IN_MB = 10240;
|
|
20
|
+
// TODO: In V5, Enable set this to 10240
|
|
21
|
+
export const DEFAULT_EPHEMERAL_STORAGE_IN_MB = 2048;
|
|
22
|
+
export const MIN_EPHEMERAL_STORAGE_IN_MB = 512;
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_OUTPUT_PRIVACY: Privacy = 'public';
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_CLOUDWATCH_RETENTION_PERIOD = 14;
|
|
27
|
+
|
|
28
|
+
export const RENDER_FN_PREFIX = 'remotion-render-';
|
|
29
|
+
export const LOG_GROUP_PREFIX = '/aws/lambda/';
|
|
30
|
+
export const LAMBDA_INSIGHTS_PREFIX = '/aws/lambda-insights';
|
|
31
|
+
|
|
32
|
+
export const getSitesKey = (siteId: string) => `sites/${siteId}`;
|
|
33
|
+
|
|
34
|
+
export type RenderProgress = GenericRenderProgress<AwsProvider>;
|
|
35
|
+
|
|
36
|
+
export const LAMBDA_CONCURRENCY_LIMIT_QUOTA = 'L-B99A9384';
|
|
37
|
+
|
|
38
|
+
export const REMOTION_BUCKET_PREFIX = 'remotionlambda-';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type {DownloadBehavior} from '@remotion/serverless-client';
|
|
2
|
+
|
|
3
|
+
// By setting the Content-Disposition header in an S3 object,
|
|
4
|
+
// you can control if the user downloads the item if you
|
|
5
|
+
// visit the link
|
|
6
|
+
|
|
7
|
+
const problematicCharacters = {
|
|
8
|
+
'%3A': ':',
|
|
9
|
+
'%2F': '/',
|
|
10
|
+
'%3F': '?',
|
|
11
|
+
'%23': '#',
|
|
12
|
+
'%5B': '[',
|
|
13
|
+
'%5D': ']',
|
|
14
|
+
'%40': '@',
|
|
15
|
+
'%21': '!',
|
|
16
|
+
'%24': '$',
|
|
17
|
+
'%26': '&',
|
|
18
|
+
'%27': "'",
|
|
19
|
+
'%28': '(',
|
|
20
|
+
'%29': ')',
|
|
21
|
+
'%2A': '*',
|
|
22
|
+
'%2B': '+',
|
|
23
|
+
'%2C': ',',
|
|
24
|
+
'%3B': ';',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type HexInfo = {
|
|
28
|
+
containsHex: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const includesHexOfUnsafeChar = (path: string): HexInfo => {
|
|
32
|
+
for (const key of Object.keys(
|
|
33
|
+
problematicCharacters,
|
|
34
|
+
) as (keyof typeof problematicCharacters)[]) {
|
|
35
|
+
if (path.includes(key)) {
|
|
36
|
+
return {containsHex: true};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {containsHex: false};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getContentDispositionHeader = (
|
|
44
|
+
behavior: DownloadBehavior | null,
|
|
45
|
+
): string | undefined => {
|
|
46
|
+
if (behavior === null) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (behavior.type === 'play-in-browser') {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (behavior.fileName === null) {
|
|
55
|
+
return `attachment`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const {containsHex} = includesHexOfUnsafeChar(behavior.fileName);
|
|
59
|
+
if (containsHex) {
|
|
60
|
+
return `attachment; filename="${behavior.fileName}"`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return `attachment; filename="${encodeURIComponent(behavior.fileName)}"`;
|
|
64
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {DOCS_URL} from '@remotion/serverless-client';
|
|
2
|
+
import type {AwsRegion} from './regions';
|
|
3
|
+
|
|
4
|
+
export const convertToServeUrlImplementation = ({
|
|
5
|
+
urlOrId,
|
|
6
|
+
region,
|
|
7
|
+
bucketName,
|
|
8
|
+
}: {
|
|
9
|
+
urlOrId: string;
|
|
10
|
+
region: AwsRegion;
|
|
11
|
+
bucketName: string;
|
|
12
|
+
}) => {
|
|
13
|
+
if (urlOrId.startsWith('src/')) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Remotion Lambda can only render based on a URL in the cloud. It seems like you passed a local file: ${urlOrId}. Read the setup guide for Remotion Lambda ${DOCS_URL}/docs/lambda/setup`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (urlOrId.startsWith('http://') || urlOrId.startsWith('https://')) {
|
|
20
|
+
return urlOrId;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return `https://${bucketName}.s3.${region}.amazonaws.com/sites/${urlOrId}/index.html`;
|
|
24
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateBucketCommand,
|
|
3
|
+
DeleteBucketOwnershipControlsCommand,
|
|
4
|
+
DeletePublicAccessBlockCommand,
|
|
5
|
+
PutBucketAclCommand,
|
|
6
|
+
} from '@aws-sdk/client-s3';
|
|
7
|
+
import type {ProviderSpecifics} from '@remotion/serverless-client';
|
|
8
|
+
import type {AwsProvider} from './aws-provider';
|
|
9
|
+
import {getS3Client} from './get-s3-client';
|
|
10
|
+
|
|
11
|
+
export const createBucket: ProviderSpecifics<AwsProvider>['createBucket'] =
|
|
12
|
+
async ({region, bucketName, forcePathStyle}) => {
|
|
13
|
+
await getS3Client({region, customCredentials: null, forcePathStyle}).send(
|
|
14
|
+
new CreateBucketCommand({
|
|
15
|
+
Bucket: bucketName,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await getS3Client({region, customCredentials: null, forcePathStyle}).send(
|
|
21
|
+
new DeleteBucketOwnershipControlsCommand({
|
|
22
|
+
Bucket: bucketName,
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if ((err as Error).message.includes('Access Denied')) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'Since April 2023, more AWS permissions are required to create an S3 bucket. You need to update your user policy to continue. See https://remotion.dev/docs/lambda/s3-public-access for instructions on how to resolve this issue.',
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await getS3Client({region, customCredentials: null, forcePathStyle}).send(
|
|
37
|
+
new DeletePublicAccessBlockCommand({
|
|
38
|
+
Bucket: bucketName,
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if ((err as Error).message.includes('Access Denied')) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
'PARTIAL SUCCESS: The s3:PutBucketOwnershipControls was found, but the s3:PutBucketPublicAccessBlock permission is not given. Since April 2023, more AWS permissions are required to create an S3 bucket. You need to update your user policy to continue. You need to update your user policy to continue. See https://remotion.dev/docs/lambda/s3-public-access for instructions on how to resolve this issue.',
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await getS3Client({region, customCredentials: null, forcePathStyle}).send(
|
|
53
|
+
new PutBucketAclCommand({
|
|
54
|
+
Bucket: bucketName,
|
|
55
|
+
ACL: 'public-read',
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if ((err as Error).message.includes('The bucket does not allow ACLs')) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Could not add an ACL to the bucket. This might have happened because the bucket was already successfully created before but then failed to configure correctly. We recommend to delete the bucket (${bucketName}) if it is empty and start over to fix the problem.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {DeleteObjectCommand} from '@aws-sdk/client-s3';
|
|
2
|
+
import type {
|
|
3
|
+
CustomCredentials,
|
|
4
|
+
ProviderSpecifics,
|
|
5
|
+
} from '@remotion/serverless-client';
|
|
6
|
+
import type {AwsProvider} from './aws-provider';
|
|
7
|
+
import {getS3Client} from './get-s3-client';
|
|
8
|
+
import type {AwsRegion} from './regions';
|
|
9
|
+
|
|
10
|
+
export const lambdaDeleteFileImplementation: ProviderSpecifics<AwsProvider>['deleteFile'] =
|
|
11
|
+
async ({
|
|
12
|
+
bucketName,
|
|
13
|
+
key,
|
|
14
|
+
region,
|
|
15
|
+
customCredentials,
|
|
16
|
+
forcePathStyle,
|
|
17
|
+
}: {
|
|
18
|
+
region: AwsRegion;
|
|
19
|
+
bucketName: string;
|
|
20
|
+
key: string;
|
|
21
|
+
customCredentials: CustomCredentials<AwsProvider> | null;
|
|
22
|
+
forcePathStyle: boolean;
|
|
23
|
+
}) => {
|
|
24
|
+
await getS3Client({region, customCredentials, forcePathStyle}).send(
|
|
25
|
+
new DeleteObjectCommand({
|
|
26
|
+
Bucket: bucketName,
|
|
27
|
+
Key: key,
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {DeleteFunctionCommand} from '@aws-sdk/client-lambda';
|
|
2
|
+
import type {
|
|
3
|
+
DeleteFunction,
|
|
4
|
+
DeleteFunctionInput as GenericDeleteFunctionInput,
|
|
5
|
+
} from '@remotion/serverless-client';
|
|
6
|
+
import {getLambdaClient} from './aws-clients';
|
|
7
|
+
import type {AwsProvider} from './aws-provider';
|
|
8
|
+
|
|
9
|
+
export type DeleteFunctionInput = GenericDeleteFunctionInput<AwsProvider>;
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* @description Deletes a deployed Lambda function based on its name.
|
|
13
|
+
* @see [Documentation](https://remotion.dev/docs/lambda/deletefunction)
|
|
14
|
+
*/
|
|
15
|
+
export const deleteFunction: DeleteFunction<AwsProvider> = async ({
|
|
16
|
+
region,
|
|
17
|
+
functionName,
|
|
18
|
+
}: DeleteFunctionInput): Promise<void> => {
|
|
19
|
+
await getLambdaClient(region).send(
|
|
20
|
+
new DeleteFunctionCommand({
|
|
21
|
+
FunctionName: functionName,
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
};
|