@hanzo/s3 0.6.4 → 8.0.7
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/LICENSE +202 -0
- package/MAINTAINERS.md +62 -0
- package/README.md +262 -0
- package/README_zh_CN.md +192 -0
- package/dist/esm/AssumeRoleProvider.d.mts +86 -0
- package/dist/esm/AssumeRoleProvider.mjs +183 -0
- package/dist/esm/CredentialProvider.d.mts +22 -0
- package/dist/esm/CredentialProvider.mjs +48 -0
- package/dist/esm/Credentials.d.mts +22 -0
- package/dist/esm/Credentials.mjs +38 -0
- package/dist/esm/IamAwsProvider.d.mts +27 -0
- package/dist/esm/IamAwsProvider.mjs +189 -0
- package/dist/esm/errors.d.mts +82 -0
- package/dist/esm/errors.mjs +117 -0
- package/dist/esm/helpers.d.mts +156 -0
- package/dist/esm/helpers.mjs +218 -0
- package/dist/esm/internal/async.d.mts +9 -0
- package/dist/esm/internal/async.mjs +14 -0
- package/dist/esm/internal/callbackify.d.mts +1 -0
- package/dist/esm/internal/callbackify.mjs +15 -0
- package/dist/esm/internal/client.d.mts +394 -0
- package/dist/esm/internal/client.mjs +3007 -0
- package/dist/esm/internal/copy-conditions.d.mts +10 -0
- package/dist/esm/internal/copy-conditions.mjs +25 -0
- package/dist/esm/internal/extensions.d.mts +18 -0
- package/dist/esm/internal/extensions.mjs +114 -0
- package/dist/esm/internal/helper.d.mts +177 -0
- package/dist/esm/internal/helper.mjs +552 -0
- package/dist/esm/internal/join-host-port.d.mts +11 -0
- package/dist/esm/internal/join-host-port.mjs +23 -0
- package/dist/esm/internal/post-policy.d.mts +17 -0
- package/dist/esm/internal/post-policy.mjs +98 -0
- package/dist/esm/internal/request.d.mts +11 -0
- package/dist/esm/internal/request.mjs +75 -0
- package/dist/esm/internal/response.d.mts +8 -0
- package/dist/esm/internal/response.mjs +16 -0
- package/dist/esm/internal/s3-endpoints.d.mts +38 -0
- package/dist/esm/internal/s3-endpoints.mjs +68 -0
- package/dist/esm/internal/type.d.mts +482 -0
- package/dist/esm/internal/type.mjs +30 -0
- package/dist/esm/internal/xml-parser.d.mts +93 -0
- package/dist/esm/internal/xml-parser.mjs +819 -0
- package/dist/esm/notification.d.mts +58 -0
- package/dist/esm/notification.mjs +209 -0
- package/dist/esm/s3.d.mts +40 -0
- package/dist/esm/s3.mjs +86 -0
- package/dist/esm/signing.d.mts +5 -0
- package/dist/esm/signing.mjs +258 -0
- package/dist/main/AssumeRoleProvider.d.ts +86 -0
- package/dist/main/AssumeRoleProvider.js +191 -0
- package/dist/main/CredentialProvider.d.ts +22 -0
- package/dist/main/CredentialProvider.js +55 -0
- package/dist/main/Credentials.d.ts +22 -0
- package/dist/main/Credentials.js +45 -0
- package/dist/main/IamAwsProvider.d.ts +27 -0
- package/dist/main/IamAwsProvider.js +198 -0
- package/dist/main/errors.d.ts +82 -0
- package/dist/main/errors.js +138 -0
- package/dist/main/helpers.d.ts +156 -0
- package/dist/main/helpers.js +233 -0
- package/dist/main/internal/async.d.ts +9 -0
- package/dist/main/internal/async.js +24 -0
- package/dist/main/internal/callbackify.d.ts +1 -0
- package/dist/main/internal/callbackify.js +21 -0
- package/dist/main/internal/client.d.ts +394 -0
- package/dist/main/internal/client.js +3014 -0
- package/dist/main/internal/copy-conditions.d.ts +10 -0
- package/dist/main/internal/copy-conditions.js +31 -0
- package/dist/main/internal/extensions.d.ts +18 -0
- package/dist/main/internal/extensions.js +122 -0
- package/dist/main/internal/helper.d.ts +177 -0
- package/dist/main/internal/helper.js +608 -0
- package/dist/main/internal/join-host-port.d.ts +11 -0
- package/dist/main/internal/join-host-port.js +29 -0
- package/dist/main/internal/post-policy.d.ts +17 -0
- package/dist/main/internal/post-policy.js +107 -0
- package/dist/main/internal/request.d.ts +11 -0
- package/dist/main/internal/request.js +83 -0
- package/dist/main/internal/response.d.ts +8 -0
- package/dist/main/internal/response.js +24 -0
- package/dist/main/internal/s3-endpoints.d.ts +38 -0
- package/dist/main/internal/s3-endpoints.js +73 -0
- package/dist/main/internal/type.d.ts +482 -0
- package/dist/main/internal/type.js +42 -0
- package/dist/main/internal/xml-parser.d.ts +93 -0
- package/dist/main/internal/xml-parser.js +849 -0
- package/dist/main/notification.d.ts +58 -0
- package/dist/main/notification.js +230 -0
- package/dist/main/s3.d.ts +40 -0
- package/dist/main/s3.js +117 -0
- package/dist/main/signing.d.ts +5 -0
- package/dist/main/signing.js +269 -0
- package/package.json +146 -39
- package/src/AssumeRoleProvider.ts +262 -0
- package/src/CredentialProvider.ts +54 -0
- package/src/Credentials.ts +44 -0
- package/src/IamAwsProvider.ts +234 -0
- package/src/errors.ts +120 -0
- package/src/helpers.ts +354 -0
- package/src/internal/async.ts +14 -0
- package/src/internal/callbackify.ts +19 -0
- package/src/internal/client.ts +3412 -0
- package/src/internal/copy-conditions.ts +30 -0
- package/src/internal/extensions.ts +140 -0
- package/src/internal/helper.ts +606 -0
- package/src/internal/join-host-port.ts +23 -0
- package/src/internal/post-policy.ts +99 -0
- package/src/internal/request.ts +102 -0
- package/src/internal/response.ts +26 -0
- package/src/internal/s3-endpoints.ts +70 -0
- package/src/internal/type.ts +577 -0
- package/src/internal/xml-parser.ts +871 -0
- package/src/notification.ts +254 -0
- package/src/s3.ts +155 -0
- package/src/signing.ts +325 -0
- package/lib/index.js +0 -450
- package/lib/index.js.map +0 -7
- package/lib/perfTest.js +0 -91
- package/lib/perfTest.js.map +0 -7
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Build PostPolicy object that can be signed by presignedPostPolicy
|
|
2
|
+
import * as errors from '../errors.ts'
|
|
3
|
+
import { isObject, isValidBucketName, isValidObjectName, isValidPrefix } from './helper.ts'
|
|
4
|
+
import type { ObjectMetaData } from './type.ts'
|
|
5
|
+
|
|
6
|
+
export class PostPolicy {
|
|
7
|
+
public policy: { conditions: (string | number)[][]; expiration?: string } = {
|
|
8
|
+
conditions: [],
|
|
9
|
+
}
|
|
10
|
+
public formData: Record<string, string> = {}
|
|
11
|
+
|
|
12
|
+
// set expiration date
|
|
13
|
+
setExpires(date: Date) {
|
|
14
|
+
if (!date) {
|
|
15
|
+
throw new errors.InvalidDateError('Invalid date: cannot be null')
|
|
16
|
+
}
|
|
17
|
+
this.policy.expiration = date.toISOString()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// set object name
|
|
21
|
+
setKey(objectName: string) {
|
|
22
|
+
if (!isValidObjectName(objectName)) {
|
|
23
|
+
throw new errors.InvalidObjectNameError(`Invalid object name : ${objectName}`)
|
|
24
|
+
}
|
|
25
|
+
this.policy.conditions.push(['eq', '$key', objectName])
|
|
26
|
+
this.formData.key = objectName
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// set object name prefix, i.e policy allows any keys with this prefix
|
|
30
|
+
setKeyStartsWith(prefix: string) {
|
|
31
|
+
if (!isValidPrefix(prefix)) {
|
|
32
|
+
throw new errors.InvalidPrefixError(`Invalid prefix : ${prefix}`)
|
|
33
|
+
}
|
|
34
|
+
this.policy.conditions.push(['starts-with', '$key', prefix])
|
|
35
|
+
this.formData.key = prefix
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// set bucket name
|
|
39
|
+
setBucket(bucketName: string) {
|
|
40
|
+
if (!isValidBucketName(bucketName)) {
|
|
41
|
+
throw new errors.InvalidBucketNameError(`Invalid bucket name : ${bucketName}`)
|
|
42
|
+
}
|
|
43
|
+
this.policy.conditions.push(['eq', '$bucket', bucketName])
|
|
44
|
+
this.formData.bucket = bucketName
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// set Content-Type
|
|
48
|
+
setContentType(type: string) {
|
|
49
|
+
if (!type) {
|
|
50
|
+
throw new Error('content-type cannot be null')
|
|
51
|
+
}
|
|
52
|
+
this.policy.conditions.push(['eq', '$Content-Type', type])
|
|
53
|
+
this.formData['Content-Type'] = type
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// set Content-Type prefix, i.e image/ allows any image
|
|
57
|
+
setContentTypeStartsWith(prefix: string) {
|
|
58
|
+
if (!prefix) {
|
|
59
|
+
throw new Error('content-type cannot be null')
|
|
60
|
+
}
|
|
61
|
+
this.policy.conditions.push(['starts-with', '$Content-Type', prefix])
|
|
62
|
+
this.formData['Content-Type'] = prefix
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// set Content-Disposition
|
|
66
|
+
setContentDisposition(value: string) {
|
|
67
|
+
if (!value) {
|
|
68
|
+
throw new Error('content-disposition cannot be null')
|
|
69
|
+
}
|
|
70
|
+
this.policy.conditions.push(['eq', '$Content-Disposition', value])
|
|
71
|
+
this.formData['Content-Disposition'] = value
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// set minimum/maximum length of what Content-Length can be.
|
|
75
|
+
setContentLengthRange(min: number, max: number) {
|
|
76
|
+
if (min > max) {
|
|
77
|
+
throw new Error('min cannot be more than max')
|
|
78
|
+
}
|
|
79
|
+
if (min < 0) {
|
|
80
|
+
throw new Error('min should be > 0')
|
|
81
|
+
}
|
|
82
|
+
if (max < 0) {
|
|
83
|
+
throw new Error('max should be > 0')
|
|
84
|
+
}
|
|
85
|
+
this.policy.conditions.push(['content-length-range', min, max])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// set user defined metadata
|
|
89
|
+
setUserMetaData(metaData: ObjectMetaData) {
|
|
90
|
+
if (!isObject(metaData)) {
|
|
91
|
+
throw new TypeError('metadata should be of type "object"')
|
|
92
|
+
}
|
|
93
|
+
Object.entries(metaData).forEach(([key, value]) => {
|
|
94
|
+
const amzMetaDataKey = `x-amz-meta-${key}`
|
|
95
|
+
this.policy.conditions.push(['eq', `$${amzMetaDataKey}`, value])
|
|
96
|
+
this.formData[amzMetaDataKey] = value.toString()
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type * as http from 'node:http'
|
|
2
|
+
import type * as https from 'node:https'
|
|
3
|
+
import type * as stream from 'node:stream'
|
|
4
|
+
import { pipeline } from 'node:stream'
|
|
5
|
+
import { promisify } from 'node:util'
|
|
6
|
+
|
|
7
|
+
import type { Transport } from './type.ts'
|
|
8
|
+
|
|
9
|
+
const pipelineAsync = promisify(pipeline)
|
|
10
|
+
|
|
11
|
+
export async function request(
|
|
12
|
+
transport: Transport,
|
|
13
|
+
opt: https.RequestOptions,
|
|
14
|
+
body: Buffer | string | stream.Readable | null = null,
|
|
15
|
+
): Promise<http.IncomingMessage> {
|
|
16
|
+
return new Promise<http.IncomingMessage>((resolve, reject) => {
|
|
17
|
+
const requestObj = transport.request(opt, (response) => {
|
|
18
|
+
resolve(response)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
requestObj.on('error', reject)
|
|
22
|
+
|
|
23
|
+
if (!body || Buffer.isBuffer(body) || typeof body === 'string') {
|
|
24
|
+
requestObj.end(body)
|
|
25
|
+
} else {
|
|
26
|
+
pipelineAsync(body, requestObj).catch(reject)
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const MAX_RETRIES = 1
|
|
32
|
+
const BASE_DELAY_MS = 100 // Base delay for exponential backoff
|
|
33
|
+
const MAX_DELAY_MS = 60000 // Max delay for exponential backoff
|
|
34
|
+
|
|
35
|
+
// Retryable error codes for HTTP (ref: Go SDK)
|
|
36
|
+
export const retryHttpCodes: Record<string, boolean> = {
|
|
37
|
+
408: true,
|
|
38
|
+
429: true,
|
|
39
|
+
499: true,
|
|
40
|
+
500: true,
|
|
41
|
+
502: true,
|
|
42
|
+
503: true,
|
|
43
|
+
504: true,
|
|
44
|
+
520: true,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isHttpRetryable = (httpResCode: number) => {
|
|
48
|
+
return retryHttpCodes[httpResCode] !== undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const sleep = (ms: number) => {
|
|
52
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const getExpBackOffDelay = (retryCount: number, baseDelayMs: number, maximumDelayMs: number) => {
|
|
56
|
+
const backOffBy = baseDelayMs * (1 << retryCount)
|
|
57
|
+
const additionalDelay = Math.random() * backOffBy
|
|
58
|
+
return Math.min(backOffBy + additionalDelay, maximumDelayMs)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function requestWithRetry(
|
|
62
|
+
transport: Transport,
|
|
63
|
+
opt: https.RequestOptions,
|
|
64
|
+
body: Buffer | string | stream.Readable | null = null,
|
|
65
|
+
maxRetries: number = MAX_RETRIES,
|
|
66
|
+
baseDelayMs: number = BASE_DELAY_MS,
|
|
67
|
+
maximumDelayMs: number = MAX_DELAY_MS,
|
|
68
|
+
): Promise<http.IncomingMessage> {
|
|
69
|
+
let attempt = 0
|
|
70
|
+
let isRetryable = false
|
|
71
|
+
while (attempt <= maxRetries) {
|
|
72
|
+
try {
|
|
73
|
+
const response = await request(transport, opt, body)
|
|
74
|
+
// Check if the HTTP status code is retryable
|
|
75
|
+
if (isHttpRetryable(response.statusCode as number)) {
|
|
76
|
+
isRetryable = true
|
|
77
|
+
throw new Error(`Retryable HTTP status: ${response.statusCode}`) // trigger retry attempt with calculated delay
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return response // Success, return the raw response
|
|
81
|
+
} catch (err: unknown) {
|
|
82
|
+
if (isRetryable) {
|
|
83
|
+
attempt++
|
|
84
|
+
isRetryable = false
|
|
85
|
+
|
|
86
|
+
if (attempt > maxRetries) {
|
|
87
|
+
throw new Error(`Request failed after ${maxRetries} retries: ${err}`)
|
|
88
|
+
}
|
|
89
|
+
const delay = getExpBackOffDelay(attempt, baseDelayMs, maximumDelayMs)
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.warn(
|
|
92
|
+
`${new Date().toLocaleString()} Retrying request (attempt ${attempt}/${maxRetries}) after ${delay}ms due to: ${err}`,
|
|
93
|
+
)
|
|
94
|
+
await sleep(delay)
|
|
95
|
+
} else {
|
|
96
|
+
throw err // re-throw if any request, syntax errors
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(`${MAX_RETRIES} Retries exhausted, request failed.`)
|
|
102
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type http from 'node:http'
|
|
2
|
+
import type stream from 'node:stream'
|
|
3
|
+
|
|
4
|
+
export async function readAsBuffer(res: stream.Readable): Promise<Buffer> {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const body: Buffer[] = []
|
|
7
|
+
res
|
|
8
|
+
.on('data', (chunk: Buffer) => body.push(chunk))
|
|
9
|
+
.on('error', (e) => reject(e))
|
|
10
|
+
.on('end', () => resolve(Buffer.concat(body)))
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function readAsString(res: http.IncomingMessage): Promise<string> {
|
|
15
|
+
const body = await readAsBuffer(res)
|
|
16
|
+
return body.toString()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function drainResponse(res: stream.Readable): Promise<void> {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
res
|
|
22
|
+
.on('data', () => {})
|
|
23
|
+
.on('error', (e) => reject(e))
|
|
24
|
+
.on('end', () => resolve())
|
|
25
|
+
})
|
|
26
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hanzo S3 Javascript Library for Amazon S3 Compatible Cloud Storage, (C) 2015, 2016 Hanzo AI, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { isString } from './helper.ts'
|
|
18
|
+
|
|
19
|
+
// List of currently supported endpoints.
|
|
20
|
+
const awsS3Endpoint = {
|
|
21
|
+
'af-south-1': 's3.af-south-1.amazonaws.com',
|
|
22
|
+
'ap-east-1': 's3.ap-east-1.amazonaws.com',
|
|
23
|
+
'ap-south-1': 's3.ap-south-1.amazonaws.com',
|
|
24
|
+
'ap-south-2': 's3.ap-south-2.amazonaws.com',
|
|
25
|
+
'ap-southeast-1': 's3.ap-southeast-1.amazonaws.com',
|
|
26
|
+
'ap-southeast-2': 's3.ap-southeast-2.amazonaws.com',
|
|
27
|
+
'ap-southeast-3': 's3.ap-southeast-3.amazonaws.com',
|
|
28
|
+
'ap-southeast-4': 's3.ap-southeast-4.amazonaws.com',
|
|
29
|
+
'ap-southeast-5': 's3.ap-southeast-5.amazonaws.com',
|
|
30
|
+
'ap-northeast-1': 's3.ap-northeast-1.amazonaws.com',
|
|
31
|
+
'ap-northeast-2': 's3.ap-northeast-2.amazonaws.com',
|
|
32
|
+
'ap-northeast-3': 's3.ap-northeast-3.amazonaws.com',
|
|
33
|
+
'ca-central-1': 's3.ca-central-1.amazonaws.com',
|
|
34
|
+
'ca-west-1': 's3.ca-west-1.amazonaws.com',
|
|
35
|
+
'cn-north-1': 's3.cn-north-1.amazonaws.com.cn',
|
|
36
|
+
'eu-central-1': 's3.eu-central-1.amazonaws.com',
|
|
37
|
+
'eu-central-2': 's3.eu-central-2.amazonaws.com',
|
|
38
|
+
'eu-north-1': 's3.eu-north-1.amazonaws.com',
|
|
39
|
+
'eu-south-1': 's3.eu-south-1.amazonaws.com',
|
|
40
|
+
'eu-south-2': 's3.eu-south-2.amazonaws.com',
|
|
41
|
+
'eu-west-1': 's3.eu-west-1.amazonaws.com',
|
|
42
|
+
'eu-west-2': 's3.eu-west-2.amazonaws.com',
|
|
43
|
+
'eu-west-3': 's3.eu-west-3.amazonaws.com',
|
|
44
|
+
'il-central-1': 's3.il-central-1.amazonaws.com',
|
|
45
|
+
'me-central-1': 's3.me-central-1.amazonaws.com',
|
|
46
|
+
'me-south-1': 's3.me-south-1.amazonaws.com',
|
|
47
|
+
'sa-east-1': 's3.sa-east-1.amazonaws.com',
|
|
48
|
+
'us-east-1': 's3.us-east-1.amazonaws.com',
|
|
49
|
+
'us-east-2': 's3.us-east-2.amazonaws.com',
|
|
50
|
+
'us-west-1': 's3.us-west-1.amazonaws.com',
|
|
51
|
+
'us-west-2': 's3.us-west-2.amazonaws.com',
|
|
52
|
+
'us-gov-east-1': 's3.us-gov-east-1.amazonaws.com',
|
|
53
|
+
'us-gov-west-1': 's3.us-gov-west-1.amazonaws.com',
|
|
54
|
+
// Add new endpoints here.
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type Region = keyof typeof awsS3Endpoint | string
|
|
58
|
+
|
|
59
|
+
// getS3Endpoint get relevant endpoint for the region.
|
|
60
|
+
export function getS3Endpoint(region: Region): string {
|
|
61
|
+
if (!isString(region)) {
|
|
62
|
+
throw new TypeError(`Invalid region: ${region}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const endpoint = (awsS3Endpoint as Record<string, string>)[region]
|
|
66
|
+
if (endpoint) {
|
|
67
|
+
return endpoint
|
|
68
|
+
}
|
|
69
|
+
return 's3.amazonaws.com'
|
|
70
|
+
}
|