@varity-labs/sdk 2.0.0-alpha.1
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 +31 -0
- package/README.md +253 -0
- package/dist/analytics/index.d.ts +7 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +6 -0
- package/dist/analytics/tracker.d.ts +128 -0
- package/dist/analytics/tracker.d.ts.map +1 -0
- package/dist/analytics/tracker.js +203 -0
- package/dist/blockchain/BlockchainService.d.ts +100 -0
- package/dist/blockchain/BlockchainService.d.ts.map +1 -0
- package/dist/blockchain/BlockchainService.js +188 -0
- package/dist/blockchain/NFTLicensingService.d.ts +69 -0
- package/dist/blockchain/NFTLicensingService.d.ts.map +1 -0
- package/dist/blockchain/NFTLicensingService.js +136 -0
- package/dist/blockchain/RevenueSplitService.d.ts +71 -0
- package/dist/blockchain/RevenueSplitService.d.ts.map +1 -0
- package/dist/blockchain/RevenueSplitService.js +111 -0
- package/dist/blockchain/index.d.ts +48 -0
- package/dist/blockchain/index.d.ts.map +1 -0
- package/dist/blockchain/index.js +46 -0
- package/dist/blockchain/types.d.ts +63 -0
- package/dist/blockchain/types.d.ts.map +1 -0
- package/dist/blockchain/types.js +6 -0
- package/dist/chains/arbitrum.d.ts +89 -0
- package/dist/chains/arbitrum.d.ts.map +1 -0
- package/dist/chains/arbitrum.js +134 -0
- package/dist/chains/base.d.ts +84 -0
- package/dist/chains/base.d.ts.map +1 -0
- package/dist/chains/base.js +131 -0
- package/dist/chains/index.d.ts +36 -0
- package/dist/chains/index.d.ts.map +1 -0
- package/dist/chains/index.js +32 -0
- package/dist/chains/registry.d.ts +113 -0
- package/dist/chains/registry.d.ts.map +1 -0
- package/dist/chains/registry.js +201 -0
- package/dist/chains/varityL3.d.ts +81 -0
- package/dist/chains/varityL3.d.ts.map +1 -0
- package/dist/chains/varityL3.js +125 -0
- package/dist/cli/commands/clone.d.ts +8 -0
- package/dist/cli/commands/clone.d.ts.map +1 -0
- package/dist/cli/commands/clone.js +391 -0
- package/dist/cli/commands/dev.d.ts +8 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +40 -0
- package/dist/cli/commands/generate.d.ts +8 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +303 -0
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +317 -0
- package/dist/cli/commands/validate.d.ts +8 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +69 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +33 -0
- package/dist/cli/utils/logger.d.ts +17 -0
- package/dist/cli/utils/logger.d.ts.map +1 -0
- package/dist/cli/utils/logger.js +35 -0
- package/dist/cli/utils/prompts.d.ts +21 -0
- package/dist/cli/utils/prompts.d.ts.map +1 -0
- package/dist/cli/utils/prompts.js +103 -0
- package/dist/contracts/abis/iso/AccessControlRegistry.json +1468 -0
- package/dist/contracts/abis/iso/DataProofRegistry.json +797 -0
- package/dist/contracts/abis/iso/MerchantRegistry.json +1237 -0
- package/dist/contracts/abis/iso/RepPerformance.json +1351 -0
- package/dist/contracts/abis/iso/ResidualCalculator.json +1118 -0
- package/dist/contracts/abis/iso/TransactionVault.json +1588 -0
- package/dist/contracts/abis/iso/VarityWalletFactory.json +475 -0
- package/dist/contracts/addresses.d.ts +88 -0
- package/dist/contracts/addresses.d.ts.map +1 -0
- package/dist/contracts/addresses.js +94 -0
- package/dist/contracts/index.d.ts +7 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +6 -0
- package/dist/core/VaritySDK.d.ts +177 -0
- package/dist/core/VaritySDK.d.ts.map +1 -0
- package/dist/core/VaritySDK.js +325 -0
- package/dist/core/config.d.ts +120 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +187 -0
- package/dist/core/credentials-proxy.d.ts +157 -0
- package/dist/core/credentials-proxy.d.ts.map +1 -0
- package/dist/core/credentials-proxy.js +345 -0
- package/dist/core/credentials.d.ts +219 -0
- package/dist/core/credentials.d.ts.map +1 -0
- package/dist/core/credentials.js +345 -0
- package/dist/core/template-loader.d.ts +15 -0
- package/dist/core/template-loader.d.ts.map +1 -0
- package/dist/core/template-loader.js +380 -0
- package/dist/core/template.d.ts +321 -0
- package/dist/core/template.d.ts.map +1 -0
- package/dist/core/template.js +189 -0
- package/dist/core/types.d.ts +572 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +52 -0
- package/dist/dev/dev-server.d.ts +16 -0
- package/dist/dev/dev-server.d.ts.map +1 -0
- package/dist/dev/dev-server.js +119 -0
- package/dist/generators/contracts/generator.d.ts +21 -0
- package/dist/generators/contracts/generator.d.ts.map +1 -0
- package/dist/generators/contracts/generator.js +252 -0
- package/dist/generators/tests/generator.d.ts +20 -0
- package/dist/generators/tests/generator.d.ts.map +1 -0
- package/dist/generators/tests/generator.js +375 -0
- package/dist/generators/types/generator.d.ts +19 -0
- package/dist/generators/types/generator.d.ts.map +1 -0
- package/dist/generators/types/generator.js +165 -0
- package/dist/generators/ui/component-generator.d.ts +20 -0
- package/dist/generators/ui/component-generator.d.ts.map +1 -0
- package/dist/generators/ui/component-generator.js +749 -0
- package/dist/generators/ui/dashboard-generator.d.ts +20 -0
- package/dist/generators/ui/dashboard-generator.d.ts.map +1 -0
- package/dist/generators/ui/dashboard-generator.js +349 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/modules/analytics/AnalyticsModule.d.ts +349 -0
- package/dist/modules/analytics/AnalyticsModule.d.ts.map +1 -0
- package/dist/modules/analytics/AnalyticsModule.js +274 -0
- package/dist/modules/analytics/index.d.ts +3 -0
- package/dist/modules/analytics/index.d.ts.map +1 -0
- package/dist/modules/analytics/index.js +1 -0
- package/dist/modules/auth/AccessKeyModule.d.ts +189 -0
- package/dist/modules/auth/AccessKeyModule.d.ts.map +1 -0
- package/dist/modules/auth/AccessKeyModule.js +322 -0
- package/dist/modules/auth/AuthModule.d.ts +133 -0
- package/dist/modules/auth/AuthModule.d.ts.map +1 -0
- package/dist/modules/auth/AuthModule.js +214 -0
- package/dist/modules/auth/index.d.ts +8 -0
- package/dist/modules/auth/index.d.ts.map +1 -0
- package/dist/modules/auth/index.js +6 -0
- package/dist/modules/cache/CacheModule.d.ts +279 -0
- package/dist/modules/cache/CacheModule.d.ts.map +1 -0
- package/dist/modules/cache/CacheModule.js +493 -0
- package/dist/modules/cache/index.d.ts +3 -0
- package/dist/modules/cache/index.d.ts.map +1 -0
- package/dist/modules/cache/index.js +1 -0
- package/dist/modules/compute/ComputeModule.d.ts +226 -0
- package/dist/modules/compute/ComputeModule.d.ts.map +1 -0
- package/dist/modules/compute/ComputeModule.js +379 -0
- package/dist/modules/compute/index.d.ts +6 -0
- package/dist/modules/compute/index.d.ts.map +1 -0
- package/dist/modules/compute/index.js +4 -0
- package/dist/modules/contracts/ContractsModule.d.ts +164 -0
- package/dist/modules/contracts/ContractsModule.d.ts.map +1 -0
- package/dist/modules/contracts/ContractsModule.js +242 -0
- package/dist/modules/contracts/index.d.ts +6 -0
- package/dist/modules/contracts/index.d.ts.map +1 -0
- package/dist/modules/contracts/index.js +4 -0
- package/dist/modules/export/ExportModule.d.ts +346 -0
- package/dist/modules/export/ExportModule.d.ts.map +1 -0
- package/dist/modules/export/ExportModule.js +432 -0
- package/dist/modules/export/index.d.ts +3 -0
- package/dist/modules/export/index.d.ts.map +1 -0
- package/dist/modules/export/index.js +1 -0
- package/dist/modules/forecasting/ForecastingModule.d.ts +579 -0
- package/dist/modules/forecasting/ForecastingModule.d.ts.map +1 -0
- package/dist/modules/forecasting/ForecastingModule.js +310 -0
- package/dist/modules/forecasting/index.d.ts +3 -0
- package/dist/modules/forecasting/index.d.ts.map +1 -0
- package/dist/modules/forecasting/index.js +1 -0
- package/dist/modules/monitoring/MonitoringModule.d.ts +359 -0
- package/dist/modules/monitoring/MonitoringModule.d.ts.map +1 -0
- package/dist/modules/monitoring/MonitoringModule.js +483 -0
- package/dist/modules/monitoring/index.d.ts +3 -0
- package/dist/modules/monitoring/index.d.ts.map +1 -0
- package/dist/modules/monitoring/index.js +1 -0
- package/dist/modules/notifications/NotificationsModule.d.ts +336 -0
- package/dist/modules/notifications/NotificationsModule.d.ts.map +1 -0
- package/dist/modules/notifications/NotificationsModule.js +418 -0
- package/dist/modules/notifications/index.d.ts +3 -0
- package/dist/modules/notifications/index.d.ts.map +1 -0
- package/dist/modules/notifications/index.js +1 -0
- package/dist/modules/oracle/OracleModule.d.ts +110 -0
- package/dist/modules/oracle/OracleModule.d.ts.map +1 -0
- package/dist/modules/oracle/OracleModule.js +151 -0
- package/dist/modules/oracle/index.d.ts +6 -0
- package/dist/modules/oracle/index.d.ts.map +1 -0
- package/dist/modules/oracle/index.js +4 -0
- package/dist/modules/storage/S3Module.d.ts +377 -0
- package/dist/modules/storage/S3Module.d.ts.map +1 -0
- package/dist/modules/storage/S3Module.js +680 -0
- package/dist/modules/storage/StorageModule.d.ts +157 -0
- package/dist/modules/storage/StorageModule.d.ts.map +1 -0
- package/dist/modules/storage/StorageModule.js +302 -0
- package/dist/modules/storage/adapters/AdapterFactory.d.ts +100 -0
- package/dist/modules/storage/adapters/AdapterFactory.d.ts.map +1 -0
- package/dist/modules/storage/adapters/AdapterFactory.js +209 -0
- package/dist/modules/storage/adapters/FilecoinAdapter.d.ts +94 -0
- package/dist/modules/storage/adapters/FilecoinAdapter.d.ts.map +1 -0
- package/dist/modules/storage/adapters/FilecoinAdapter.js +263 -0
- package/dist/modules/storage/adapters/IStorageAdapter.d.ts +287 -0
- package/dist/modules/storage/adapters/IStorageAdapter.d.ts.map +1 -0
- package/dist/modules/storage/adapters/IStorageAdapter.js +81 -0
- package/dist/modules/storage/adapters/MultiTierAdapter.d.ts +187 -0
- package/dist/modules/storage/adapters/MultiTierAdapter.d.ts.map +1 -0
- package/dist/modules/storage/adapters/MultiTierAdapter.js +430 -0
- package/dist/modules/storage/adapters/index.d.ts +12 -0
- package/dist/modules/storage/adapters/index.d.ts.map +1 -0
- package/dist/modules/storage/adapters/index.js +12 -0
- package/dist/modules/storage/index.d.ts +16 -0
- package/dist/modules/storage/index.d.ts.map +1 -0
- package/dist/modules/storage/index.js +15 -0
- package/dist/modules/storage/tiering/AccessAnalyzer.d.ts +227 -0
- package/dist/modules/storage/tiering/AccessAnalyzer.d.ts.map +1 -0
- package/dist/modules/storage/tiering/AccessAnalyzer.js +367 -0
- package/dist/modules/storage/tiering/CostOptimizer.d.ts +248 -0
- package/dist/modules/storage/tiering/CostOptimizer.d.ts.map +1 -0
- package/dist/modules/storage/tiering/CostOptimizer.js +356 -0
- package/dist/modules/storage/tiering/MetadataStore.d.ts +287 -0
- package/dist/modules/storage/tiering/MetadataStore.d.ts.map +1 -0
- package/dist/modules/storage/tiering/MetadataStore.js +535 -0
- package/dist/modules/storage/tiering/TieringEngine.d.ts +237 -0
- package/dist/modules/storage/tiering/TieringEngine.d.ts.map +1 -0
- package/dist/modules/storage/tiering/TieringEngine.js +419 -0
- package/dist/modules/storage/tiering/example.d.ts +8 -0
- package/dist/modules/storage/tiering/example.d.ts.map +1 -0
- package/dist/modules/storage/tiering/example.js +250 -0
- package/dist/modules/storage/tiering/index.d.ts +17 -0
- package/dist/modules/storage/tiering/index.d.ts.map +1 -0
- package/dist/modules/storage/tiering/index.js +13 -0
- package/dist/modules/webhooks/WebhooksModule.d.ts +476 -0
- package/dist/modules/webhooks/WebhooksModule.d.ts.map +1 -0
- package/dist/modules/webhooks/WebhooksModule.js +359 -0
- package/dist/modules/webhooks/index.d.ts +3 -0
- package/dist/modules/webhooks/index.d.ts.map +1 -0
- package/dist/modules/webhooks/index.js +1 -0
- package/dist/modules/zk/ZKModule.d.ts +153 -0
- package/dist/modules/zk/ZKModule.d.ts.map +1 -0
- package/dist/modules/zk/ZKModule.js +262 -0
- package/dist/modules/zk/index.d.ts +7 -0
- package/dist/modules/zk/index.d.ts.map +1 -0
- package/dist/modules/zk/index.js +4 -0
- package/dist/thirdweb/BridgeClient.d.ts +228 -0
- package/dist/thirdweb/BridgeClient.d.ts.map +1 -0
- package/dist/thirdweb/BridgeClient.js +160 -0
- package/dist/thirdweb/EngineClient.d.ts +396 -0
- package/dist/thirdweb/EngineClient.d.ts.map +1 -0
- package/dist/thirdweb/EngineClient.js +386 -0
- package/dist/thirdweb/GatewayClient.d.ts +190 -0
- package/dist/thirdweb/GatewayClient.d.ts.map +1 -0
- package/dist/thirdweb/GatewayClient.js +257 -0
- package/dist/thirdweb/NebulaClient.d.ts +292 -0
- package/dist/thirdweb/NebulaClient.d.ts.map +1 -0
- package/dist/thirdweb/NebulaClient.js +180 -0
- package/dist/thirdweb/StorageClient.d.ts +445 -0
- package/dist/thirdweb/StorageClient.d.ts.map +1 -0
- package/dist/thirdweb/StorageClient.js +405 -0
- package/dist/thirdweb/ThirdwebWrapper.d.ts +236 -0
- package/dist/thirdweb/ThirdwebWrapper.d.ts.map +1 -0
- package/dist/thirdweb/ThirdwebWrapper.js +332 -0
- package/dist/thirdweb/index.d.ts +21 -0
- package/dist/thirdweb/index.d.ts.map +1 -0
- package/dist/thirdweb/index.js +28 -0
- package/dist/thirdweb/varity-chain.d.ts +48 -0
- package/dist/thirdweb/varity-chain.d.ts.map +1 -0
- package/dist/thirdweb/varity-chain.js +64 -0
- package/dist/thirdweb/x402Client.d.ts +319 -0
- package/dist/thirdweb/x402Client.d.ts.map +1 -0
- package/dist/thirdweb/x402Client.js +223 -0
- package/dist/tracking/gasTracker.d.ts +158 -0
- package/dist/tracking/gasTracker.d.ts.map +1 -0
- package/dist/tracking/gasTracker.js +227 -0
- package/dist/tracking/index.d.ts +10 -0
- package/dist/tracking/index.d.ts.map +1 -0
- package/dist/tracking/index.js +8 -0
- package/dist/tracking/types.d.ts +327 -0
- package/dist/tracking/types.d.ts.map +1 -0
- package/dist/tracking/types.js +8 -0
- package/dist/ui/components/ChartWidget.d.ts +36 -0
- package/dist/ui/components/ChartWidget.d.ts.map +1 -0
- package/dist/ui/components/ChartWidget.js +82 -0
- package/dist/ui/components/DashboardLayout.d.ts +41 -0
- package/dist/ui/components/DashboardLayout.d.ts.map +1 -0
- package/dist/ui/components/DashboardLayout.js +102 -0
- package/dist/ui/components/DataTable.d.ts +49 -0
- package/dist/ui/components/DataTable.d.ts.map +1 -0
- package/dist/ui/components/DataTable.js +96 -0
- package/dist/ui/components/EntityForm.d.ts +60 -0
- package/dist/ui/components/EntityForm.d.ts.map +1 -0
- package/dist/ui/components/EntityForm.js +182 -0
- package/dist/ui/components/KPICard.d.ts +29 -0
- package/dist/ui/components/KPICard.d.ts.map +1 -0
- package/dist/ui/components/KPICard.js +61 -0
- package/dist/ui/components/Modal.d.ts +34 -0
- package/dist/ui/components/Modal.d.ts.map +1 -0
- package/dist/ui/components/Modal.js +30 -0
- package/dist/ui/components/index.d.ts +18 -0
- package/dist/ui/components/index.d.ts.map +1 -0
- package/dist/ui/components/index.js +11 -0
- package/dist/validation/template-validator.d.ts +25 -0
- package/dist/validation/template-validator.d.ts.map +1 -0
- package/dist/validation/template-validator.js +305 -0
- package/package.json +102 -0
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Varity SDK - S3-Compatible Storage Module
|
|
3
|
+
*
|
|
4
|
+
* Provides S3-compatible methods backed by Filecoin/IPFS via S3 Gateway.
|
|
5
|
+
* Developers can use familiar S3 APIs (putObject, getObject, etc.) while
|
|
6
|
+
* benefiting from decentralized storage.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const sdk = new VaritySDK({
|
|
11
|
+
* network: 'arbitrum-sepolia',
|
|
12
|
+
* s3Config: {
|
|
13
|
+
* endpoint: 'http://localhost:3001',
|
|
14
|
+
* accessKeyId: 'test-key',
|
|
15
|
+
* secretAccessKey: 'test-secret',
|
|
16
|
+
* bucket: 'my-bucket',
|
|
17
|
+
* region: 'us-east-1'
|
|
18
|
+
* }
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* // Upload object
|
|
22
|
+
* const result = await sdk.s3.putObject({
|
|
23
|
+
* Bucket: 'my-bucket',
|
|
24
|
+
* Key: 'document.txt',
|
|
25
|
+
* Body: 'Hello, World!'
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* // Download object
|
|
29
|
+
* const data = await sdk.s3.getObject({
|
|
30
|
+
* Bucket: 'my-bucket',
|
|
31
|
+
* Key: 'document.txt'
|
|
32
|
+
* })
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
import { StorageBackend } from '@varity-labs/types';
|
|
36
|
+
import { createHmac, createHash } from 'crypto';
|
|
37
|
+
/**
|
|
38
|
+
* S3Module - S3-Compatible Storage Interface
|
|
39
|
+
*
|
|
40
|
+
* Provides familiar S3 API methods backed by Filecoin/IPFS via S3 Gateway (Agent 2).
|
|
41
|
+
*/
|
|
42
|
+
export class S3Module {
|
|
43
|
+
constructor(sdk, config) {
|
|
44
|
+
this.sdk = sdk;
|
|
45
|
+
this.config = config;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* PUT object - S3-compatible upload
|
|
49
|
+
*
|
|
50
|
+
* @param params - PutObject parameters
|
|
51
|
+
* @returns Upload result with S3 metadata
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const result = await sdk.s3.putObject({
|
|
56
|
+
* Bucket: 'my-bucket',
|
|
57
|
+
* Key: 'document.txt',
|
|
58
|
+
* Body: 'Hello, World!',
|
|
59
|
+
* ContentType: 'text/plain'
|
|
60
|
+
* })
|
|
61
|
+
* console.log(result.ETag)
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
async putObject(params) {
|
|
65
|
+
const endpoint = this.buildEndpoint();
|
|
66
|
+
const url = `${endpoint}/${params.Bucket}/${params.Key}`;
|
|
67
|
+
// Convert Body to Buffer
|
|
68
|
+
let body;
|
|
69
|
+
if (Buffer.isBuffer(params.Body)) {
|
|
70
|
+
body = params.Body;
|
|
71
|
+
}
|
|
72
|
+
else if (typeof params.Body === 'string') {
|
|
73
|
+
body = Buffer.from(params.Body, 'utf-8');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Blob
|
|
77
|
+
body = Buffer.from(await params.Body.arrayBuffer());
|
|
78
|
+
}
|
|
79
|
+
// Build headers
|
|
80
|
+
const headers = await this.buildAuthHeaders('PUT', params.Bucket, params.Key, {
|
|
81
|
+
'Content-Type': params.ContentType || 'application/octet-stream',
|
|
82
|
+
'Content-Length': body.length.toString(),
|
|
83
|
+
...(params.ContentEncoding && { 'Content-Encoding': params.ContentEncoding }),
|
|
84
|
+
...(params.ContentLanguage && { 'Content-Language': params.ContentLanguage }),
|
|
85
|
+
...(params.CacheControl && { 'Cache-Control': params.CacheControl }),
|
|
86
|
+
...(params.ContentDisposition && { 'Content-Disposition': params.ContentDisposition }),
|
|
87
|
+
...(params.ServerSideEncryption && { 'x-amz-server-side-encryption': params.ServerSideEncryption }),
|
|
88
|
+
...(params.SSEKMSKeyId && { 'x-amz-server-side-encryption-aws-kms-key-id': params.SSEKMSKeyId }),
|
|
89
|
+
...(params.StorageClass && { 'x-amz-storage-class': params.StorageClass }),
|
|
90
|
+
...(params.ACL && { 'x-amz-acl': params.ACL }),
|
|
91
|
+
...(params.Tagging && { 'x-amz-tagging': params.Tagging }),
|
|
92
|
+
...(params.Metadata && this.buildMetadataHeaders(params.Metadata))
|
|
93
|
+
});
|
|
94
|
+
const response = await fetch(url, {
|
|
95
|
+
method: 'PUT',
|
|
96
|
+
headers,
|
|
97
|
+
body: new Blob([new Uint8Array(body)])
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const errorText = await response.text();
|
|
101
|
+
throw new Error(`S3 putObject failed: ${response.statusText} - ${errorText}`);
|
|
102
|
+
}
|
|
103
|
+
const etag = response.headers.get('ETag') || '';
|
|
104
|
+
const versionId = response.headers.get('x-amz-version-id') || undefined;
|
|
105
|
+
return {
|
|
106
|
+
backend: StorageBackend.FILECOIN_IPFS,
|
|
107
|
+
identifier: params.Key,
|
|
108
|
+
gatewayUrl: url,
|
|
109
|
+
size: body.length,
|
|
110
|
+
hash: this.calculateSHA256(body),
|
|
111
|
+
timestamp: Date.now(),
|
|
112
|
+
s3Key: params.Key,
|
|
113
|
+
bucket: params.Bucket,
|
|
114
|
+
region: this.config.region,
|
|
115
|
+
etag: etag.replace(/"/g, ''),
|
|
116
|
+
versionId,
|
|
117
|
+
storageClass: params.StorageClass
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* GET object - S3-compatible download
|
|
122
|
+
*
|
|
123
|
+
* @param params - GetObject parameters
|
|
124
|
+
* @returns Object data and metadata
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const result = await sdk.s3.getObject({
|
|
129
|
+
* Bucket: 'my-bucket',
|
|
130
|
+
* Key: 'document.txt'
|
|
131
|
+
* })
|
|
132
|
+
* console.log(result.Body.toString())
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
async getObject(params) {
|
|
136
|
+
const endpoint = this.buildEndpoint();
|
|
137
|
+
const url = `${endpoint}/${params.Bucket}/${params.Key}${params.VersionId ? `?versionId=${params.VersionId}` : ''}`;
|
|
138
|
+
const headers = await this.buildAuthHeaders('GET', params.Bucket, params.Key, {
|
|
139
|
+
...(params.Range && { 'Range': params.Range }),
|
|
140
|
+
...(params.IfMatch && { 'If-Match': params.IfMatch }),
|
|
141
|
+
...(params.IfNoneMatch && { 'If-None-Match': params.IfNoneMatch }),
|
|
142
|
+
...(params.IfModifiedSince && { 'If-Modified-Since': params.IfModifiedSince.toUTCString() }),
|
|
143
|
+
...(params.IfUnmodifiedSince && { 'If-Unmodified-Since': params.IfUnmodifiedSince.toUTCString() })
|
|
144
|
+
});
|
|
145
|
+
const response = await fetch(url, { headers });
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
const errorText = await response.text();
|
|
148
|
+
throw new Error(`S3 getObject failed: ${response.statusText} - ${errorText}`);
|
|
149
|
+
}
|
|
150
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
151
|
+
const metadata = this.extractMetadata(response.headers);
|
|
152
|
+
return {
|
|
153
|
+
Body: body,
|
|
154
|
+
ContentType: response.headers.get('Content-Type') || 'application/octet-stream',
|
|
155
|
+
ContentLength: parseInt(response.headers.get('Content-Length') || '0', 10),
|
|
156
|
+
ETag: (response.headers.get('ETag') || '').replace(/"/g, ''),
|
|
157
|
+
LastModified: new Date(response.headers.get('Last-Modified') || Date.now()),
|
|
158
|
+
Metadata: metadata,
|
|
159
|
+
VersionId: response.headers.get('x-amz-version-id') || undefined,
|
|
160
|
+
StorageClass: response.headers.get('x-amz-storage-class') || undefined
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* DELETE object - S3-compatible deletion
|
|
165
|
+
*
|
|
166
|
+
* @param params - DeleteObject parameters
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* await sdk.s3.deleteObject({
|
|
171
|
+
* Bucket: 'my-bucket',
|
|
172
|
+
* Key: 'document.txt'
|
|
173
|
+
* })
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
async deleteObject(params) {
|
|
177
|
+
const endpoint = this.buildEndpoint();
|
|
178
|
+
const url = `${endpoint}/${params.Bucket}/${params.Key}${params.VersionId ? `?versionId=${params.VersionId}` : ''}`;
|
|
179
|
+
const headers = await this.buildAuthHeaders('DELETE', params.Bucket, params.Key);
|
|
180
|
+
const response = await fetch(url, {
|
|
181
|
+
method: 'DELETE',
|
|
182
|
+
headers
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const errorText = await response.text();
|
|
186
|
+
throw new Error(`S3 deleteObject failed: ${response.statusText} - ${errorText}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* LIST objects - S3-compatible list operation
|
|
191
|
+
*
|
|
192
|
+
* @param params - ListObjects parameters
|
|
193
|
+
* @returns List of objects
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const result = await sdk.s3.listObjects({
|
|
198
|
+
* Bucket: 'my-bucket',
|
|
199
|
+
* Prefix: 'documents/'
|
|
200
|
+
* })
|
|
201
|
+
* console.log(result.objects.length)
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
async listObjects(params) {
|
|
205
|
+
const endpoint = this.buildEndpoint();
|
|
206
|
+
const queryParams = new URLSearchParams();
|
|
207
|
+
queryParams.append('list-type', '2');
|
|
208
|
+
if (params.Prefix)
|
|
209
|
+
queryParams.append('prefix', params.Prefix);
|
|
210
|
+
if (params.Delimiter)
|
|
211
|
+
queryParams.append('delimiter', params.Delimiter);
|
|
212
|
+
if (params.MaxKeys)
|
|
213
|
+
queryParams.append('max-keys', params.MaxKeys.toString());
|
|
214
|
+
if (params.ContinuationToken)
|
|
215
|
+
queryParams.append('continuation-token', params.ContinuationToken);
|
|
216
|
+
if (params.StartAfter)
|
|
217
|
+
queryParams.append('start-after', params.StartAfter);
|
|
218
|
+
const url = `${endpoint}/${params.Bucket}?${queryParams}`;
|
|
219
|
+
const headers = await this.buildAuthHeaders('GET', params.Bucket, '');
|
|
220
|
+
const response = await fetch(url, { headers });
|
|
221
|
+
if (!response.ok) {
|
|
222
|
+
const errorText = await response.text();
|
|
223
|
+
throw new Error(`S3 listObjects failed: ${response.statusText} - ${errorText}`);
|
|
224
|
+
}
|
|
225
|
+
const xml = await response.text();
|
|
226
|
+
return this.parseListObjectsResponse(xml);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* HEAD object - Get object metadata without downloading
|
|
230
|
+
*
|
|
231
|
+
* @param params - HeadObject parameters
|
|
232
|
+
* @returns Object metadata
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* const metadata = await sdk.s3.headObject({
|
|
237
|
+
* Bucket: 'my-bucket',
|
|
238
|
+
* Key: 'document.txt'
|
|
239
|
+
* })
|
|
240
|
+
* console.log(metadata.ContentLength)
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
async headObject(params) {
|
|
244
|
+
const endpoint = this.buildEndpoint();
|
|
245
|
+
const url = `${endpoint}/${params.Bucket}/${params.Key}${params.VersionId ? `?versionId=${params.VersionId}` : ''}`;
|
|
246
|
+
const headers = await this.buildAuthHeaders('HEAD', params.Bucket, params.Key);
|
|
247
|
+
const response = await fetch(url, {
|
|
248
|
+
method: 'HEAD',
|
|
249
|
+
headers
|
|
250
|
+
});
|
|
251
|
+
if (!response.ok) {
|
|
252
|
+
throw new Error(`S3 headObject failed: ${response.statusText}`);
|
|
253
|
+
}
|
|
254
|
+
const metadata = this.extractMetadata(response.headers);
|
|
255
|
+
return {
|
|
256
|
+
ContentType: response.headers.get('Content-Type') || 'application/octet-stream',
|
|
257
|
+
ContentLength: parseInt(response.headers.get('Content-Length') || '0', 10),
|
|
258
|
+
ETag: (response.headers.get('ETag') || '').replace(/"/g, ''),
|
|
259
|
+
LastModified: new Date(response.headers.get('Last-Modified') || Date.now()),
|
|
260
|
+
Metadata: metadata,
|
|
261
|
+
VersionId: response.headers.get('x-amz-version-id') || undefined,
|
|
262
|
+
StorageClass: response.headers.get('x-amz-storage-class') || undefined
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Generate presigned URL for object access
|
|
267
|
+
*
|
|
268
|
+
* @param bucket - Bucket name
|
|
269
|
+
* @param key - Object key
|
|
270
|
+
* @param options - Presigned URL options
|
|
271
|
+
* @returns Presigned URL
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* const presignedUrl = await sdk.s3.getPresignedUrl(
|
|
276
|
+
* 'my-bucket',
|
|
277
|
+
* 'document.txt',
|
|
278
|
+
* { expiresIn: 3600, method: 'GET' }
|
|
279
|
+
* )
|
|
280
|
+
* console.log(presignedUrl.url)
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
async getPresignedUrl(bucket, key, options) {
|
|
284
|
+
const endpoint = this.buildEndpoint();
|
|
285
|
+
const method = options.method || 'GET';
|
|
286
|
+
const expiresIn = options.expiresIn;
|
|
287
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
288
|
+
// Build base URL
|
|
289
|
+
let url = `${endpoint}/${bucket}/${key}`;
|
|
290
|
+
// Add query parameters
|
|
291
|
+
const params = new URLSearchParams();
|
|
292
|
+
params.append('X-Amz-Algorithm', 'AWS4-HMAC-SHA256');
|
|
293
|
+
params.append('X-Amz-Credential', `${this.config.accessKeyId}/${this.getCredentialScope()}`);
|
|
294
|
+
params.append('X-Amz-Date', this.getAmzDate());
|
|
295
|
+
params.append('X-Amz-Expires', expiresIn.toString());
|
|
296
|
+
params.append('X-Amz-SignedHeaders', 'host');
|
|
297
|
+
// Add response headers if specified
|
|
298
|
+
if (options.responseHeaders) {
|
|
299
|
+
if (options.responseHeaders.contentType) {
|
|
300
|
+
params.append('response-content-type', options.responseHeaders.contentType);
|
|
301
|
+
}
|
|
302
|
+
if (options.responseHeaders.contentDisposition) {
|
|
303
|
+
params.append('response-content-disposition', options.responseHeaders.contentDisposition);
|
|
304
|
+
}
|
|
305
|
+
if (options.responseHeaders.cacheControl) {
|
|
306
|
+
params.append('response-cache-control', options.responseHeaders.cacheControl);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Add version ID if specified
|
|
310
|
+
if (options.versionId) {
|
|
311
|
+
params.append('versionId', options.versionId);
|
|
312
|
+
}
|
|
313
|
+
// Generate signature
|
|
314
|
+
const stringToSign = this.buildStringToSign(method, bucket, key, params);
|
|
315
|
+
const signature = this.signString(stringToSign);
|
|
316
|
+
params.append('X-Amz-Signature', signature);
|
|
317
|
+
url += `?${params}`;
|
|
318
|
+
return {
|
|
319
|
+
url,
|
|
320
|
+
expiresAt,
|
|
321
|
+
method,
|
|
322
|
+
headers: options.requestHeaders
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Initiate multipart upload
|
|
327
|
+
*
|
|
328
|
+
* @param bucket - Bucket name
|
|
329
|
+
* @param key - Object key
|
|
330
|
+
* @param options - Multipart upload options
|
|
331
|
+
* @returns Upload session
|
|
332
|
+
*/
|
|
333
|
+
async createMultipartUpload(bucket, key, options) {
|
|
334
|
+
const endpoint = this.buildEndpoint();
|
|
335
|
+
const url = `${endpoint}/${bucket}/${key}?uploads`;
|
|
336
|
+
const headers = await this.buildAuthHeaders('POST', bucket, key, {
|
|
337
|
+
...(options?.serverSideEncryption && { 'x-amz-server-side-encryption': options.serverSideEncryption }),
|
|
338
|
+
...(options?.kmsKeyId && { 'x-amz-server-side-encryption-aws-kms-key-id': options.kmsKeyId }),
|
|
339
|
+
...(options?.storageClass && { 'x-amz-storage-class': options.storageClass }),
|
|
340
|
+
...(options?.metadata && this.buildMetadataHeaders(options.metadata))
|
|
341
|
+
});
|
|
342
|
+
const response = await fetch(url, {
|
|
343
|
+
method: 'POST',
|
|
344
|
+
headers
|
|
345
|
+
});
|
|
346
|
+
if (!response.ok) {
|
|
347
|
+
const errorText = await response.text();
|
|
348
|
+
throw new Error(`S3 createMultipartUpload failed: ${response.statusText} - ${errorText}`);
|
|
349
|
+
}
|
|
350
|
+
const xml = await response.text();
|
|
351
|
+
const uploadId = this.extractUploadId(xml);
|
|
352
|
+
return {
|
|
353
|
+
uploadId,
|
|
354
|
+
bucket,
|
|
355
|
+
key,
|
|
356
|
+
parts: [],
|
|
357
|
+
initiated: new Date()
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Upload part for multipart upload
|
|
362
|
+
*
|
|
363
|
+
* @param bucket - Bucket name
|
|
364
|
+
* @param key - Object key
|
|
365
|
+
* @param uploadId - Upload ID
|
|
366
|
+
* @param partNumber - Part number (1-10000)
|
|
367
|
+
* @param body - Part data
|
|
368
|
+
* @returns Upload part result
|
|
369
|
+
*/
|
|
370
|
+
async uploadPart(bucket, key, uploadId, partNumber, body) {
|
|
371
|
+
const endpoint = this.buildEndpoint();
|
|
372
|
+
const url = `${endpoint}/${bucket}/${key}?partNumber=${partNumber}&uploadId=${uploadId}`;
|
|
373
|
+
const headers = await this.buildAuthHeaders('PUT', bucket, key, {
|
|
374
|
+
'Content-Length': body.length.toString()
|
|
375
|
+
});
|
|
376
|
+
const response = await fetch(url, {
|
|
377
|
+
method: 'PUT',
|
|
378
|
+
headers,
|
|
379
|
+
body: new Blob([new Uint8Array(body)])
|
|
380
|
+
});
|
|
381
|
+
if (!response.ok) {
|
|
382
|
+
const errorText = await response.text();
|
|
383
|
+
throw new Error(`S3 uploadPart failed: ${response.statusText} - ${errorText}`);
|
|
384
|
+
}
|
|
385
|
+
const etag = (response.headers.get('ETag') || '').replace(/"/g, '');
|
|
386
|
+
return {
|
|
387
|
+
partNumber,
|
|
388
|
+
etag,
|
|
389
|
+
size: body.length,
|
|
390
|
+
lastModified: new Date()
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Complete multipart upload
|
|
395
|
+
*
|
|
396
|
+
* @param bucket - Bucket name
|
|
397
|
+
* @param key - Object key
|
|
398
|
+
* @param uploadId - Upload ID
|
|
399
|
+
* @param parts - Uploaded parts
|
|
400
|
+
* @returns Upload result
|
|
401
|
+
*/
|
|
402
|
+
async completeMultipartUpload(bucket, key, uploadId, parts) {
|
|
403
|
+
const endpoint = this.buildEndpoint();
|
|
404
|
+
const url = `${endpoint}/${bucket}/${key}?uploadId=${uploadId}`;
|
|
405
|
+
// Build XML body
|
|
406
|
+
const xml = this.buildCompleteMultipartUploadXML(parts);
|
|
407
|
+
const body = Buffer.from(xml, 'utf-8');
|
|
408
|
+
const headers = await this.buildAuthHeaders('POST', bucket, key, {
|
|
409
|
+
'Content-Type': 'application/xml',
|
|
410
|
+
'Content-Length': body.length.toString()
|
|
411
|
+
});
|
|
412
|
+
const response = await fetch(url, {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
headers,
|
|
415
|
+
body
|
|
416
|
+
});
|
|
417
|
+
if (!response.ok) {
|
|
418
|
+
const errorText = await response.text();
|
|
419
|
+
throw new Error(`S3 completeMultipartUpload failed: ${response.statusText} - ${errorText}`);
|
|
420
|
+
}
|
|
421
|
+
const etag = (response.headers.get('ETag') || '').replace(/"/g, '');
|
|
422
|
+
const totalSize = parts.reduce((sum, part) => sum + part.size, 0);
|
|
423
|
+
return {
|
|
424
|
+
backend: StorageBackend.FILECOIN_IPFS,
|
|
425
|
+
identifier: key,
|
|
426
|
+
gatewayUrl: `${endpoint}/${bucket}/${key}`,
|
|
427
|
+
size: totalSize,
|
|
428
|
+
hash: this.calculateSHA256(body),
|
|
429
|
+
timestamp: Date.now(),
|
|
430
|
+
s3Key: key,
|
|
431
|
+
bucket,
|
|
432
|
+
region: this.config.region,
|
|
433
|
+
etag
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Abort multipart upload
|
|
438
|
+
*
|
|
439
|
+
* @param bucket - Bucket name
|
|
440
|
+
* @param key - Object key
|
|
441
|
+
* @param uploadId - Upload ID
|
|
442
|
+
*/
|
|
443
|
+
async abortMultipartUpload(bucket, key, uploadId) {
|
|
444
|
+
const endpoint = this.buildEndpoint();
|
|
445
|
+
const url = `${endpoint}/${bucket}/${key}?uploadId=${uploadId}`;
|
|
446
|
+
const headers = await this.buildAuthHeaders('DELETE', bucket, key);
|
|
447
|
+
const response = await fetch(url, {
|
|
448
|
+
method: 'DELETE',
|
|
449
|
+
headers
|
|
450
|
+
});
|
|
451
|
+
if (!response.ok) {
|
|
452
|
+
const errorText = await response.text();
|
|
453
|
+
throw new Error(`S3 abortMultipartUpload failed: ${response.statusText} - ${errorText}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// ============================================================================
|
|
457
|
+
// Private Helper Methods
|
|
458
|
+
// ============================================================================
|
|
459
|
+
/**
|
|
460
|
+
* Build endpoint URL
|
|
461
|
+
*/
|
|
462
|
+
buildEndpoint() {
|
|
463
|
+
const protocol = this.config.useSSL !== false ? 'https' : 'http';
|
|
464
|
+
const port = this.config.port || (this.config.useSSL !== false ? 443 : 80);
|
|
465
|
+
const portSuffix = (port === 443 && this.config.useSSL !== false) || (port === 80 && this.config.useSSL === false) ? '' : `:${port}`;
|
|
466
|
+
return `${protocol}://${this.config.endpoint}${portSuffix}`;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Build AWS Signature V4 headers
|
|
470
|
+
*/
|
|
471
|
+
async buildAuthHeaders(method, bucket, key, additionalHeaders = {}) {
|
|
472
|
+
const host = this.config.endpoint;
|
|
473
|
+
const amzDate = this.getAmzDate();
|
|
474
|
+
const dateStamp = amzDate.substring(0, 8);
|
|
475
|
+
const headers = {
|
|
476
|
+
'host': host,
|
|
477
|
+
'x-amz-date': amzDate,
|
|
478
|
+
...additionalHeaders
|
|
479
|
+
};
|
|
480
|
+
// Build canonical request
|
|
481
|
+
const canonicalUri = `/${bucket}/${key}`.replace(/\/+/g, '/');
|
|
482
|
+
const canonicalQueryString = '';
|
|
483
|
+
const canonicalHeaders = Object.keys(headers)
|
|
484
|
+
.sort()
|
|
485
|
+
.map(key => `${key.toLowerCase()}:${headers[key].trim()}`)
|
|
486
|
+
.join('\n') + '\n';
|
|
487
|
+
const signedHeaders = Object.keys(headers)
|
|
488
|
+
.sort()
|
|
489
|
+
.map(key => key.toLowerCase())
|
|
490
|
+
.join(';');
|
|
491
|
+
const payloadHash = 'UNSIGNED-PAYLOAD';
|
|
492
|
+
const canonicalRequest = [
|
|
493
|
+
method,
|
|
494
|
+
canonicalUri,
|
|
495
|
+
canonicalQueryString,
|
|
496
|
+
canonicalHeaders,
|
|
497
|
+
signedHeaders,
|
|
498
|
+
payloadHash
|
|
499
|
+
].join('\n');
|
|
500
|
+
// Build string to sign
|
|
501
|
+
const credentialScope = `${dateStamp}/${this.config.region || 'us-east-1'}/s3/aws4_request`;
|
|
502
|
+
const stringToSign = [
|
|
503
|
+
'AWS4-HMAC-SHA256',
|
|
504
|
+
amzDate,
|
|
505
|
+
credentialScope,
|
|
506
|
+
this.sha256(canonicalRequest)
|
|
507
|
+
].join('\n');
|
|
508
|
+
// Calculate signature
|
|
509
|
+
const signingKey = this.getSignatureKey(this.config.secretAccessKey, dateStamp, this.config.region || 'us-east-1', 's3');
|
|
510
|
+
const signature = createHmac('sha256', signingKey)
|
|
511
|
+
.update(stringToSign)
|
|
512
|
+
.digest('hex');
|
|
513
|
+
// Build authorization header
|
|
514
|
+
const authorizationHeader = [
|
|
515
|
+
'AWS4-HMAC-SHA256',
|
|
516
|
+
`Credential=${this.config.accessKeyId}/${credentialScope}`,
|
|
517
|
+
`SignedHeaders=${signedHeaders}`,
|
|
518
|
+
`Signature=${signature}`
|
|
519
|
+
].join(' ');
|
|
520
|
+
headers['Authorization'] = authorizationHeader;
|
|
521
|
+
return headers;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get AMZ date (ISO 8601 format)
|
|
525
|
+
*/
|
|
526
|
+
getAmzDate() {
|
|
527
|
+
return new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Get credential scope
|
|
531
|
+
*/
|
|
532
|
+
getCredentialScope() {
|
|
533
|
+
const dateStamp = this.getAmzDate().substring(0, 8);
|
|
534
|
+
return `${dateStamp}/${this.config.region || 'us-east-1'}/s3/aws4_request`;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Build string to sign for presigned URL
|
|
538
|
+
*/
|
|
539
|
+
buildStringToSign(method, bucket, key, params) {
|
|
540
|
+
const amzDate = this.getAmzDate();
|
|
541
|
+
const dateStamp = amzDate.substring(0, 8);
|
|
542
|
+
const credentialScope = `${dateStamp}/${this.config.region || 'us-east-1'}/s3/aws4_request`;
|
|
543
|
+
const canonicalUri = `/${bucket}/${key}`;
|
|
544
|
+
const canonicalQueryString = params.toString();
|
|
545
|
+
const canonicalHeaders = `host:${this.config.endpoint}\n`;
|
|
546
|
+
const signedHeaders = 'host';
|
|
547
|
+
const payloadHash = 'UNSIGNED-PAYLOAD';
|
|
548
|
+
const canonicalRequest = [
|
|
549
|
+
method,
|
|
550
|
+
canonicalUri,
|
|
551
|
+
canonicalQueryString,
|
|
552
|
+
canonicalHeaders,
|
|
553
|
+
signedHeaders,
|
|
554
|
+
payloadHash
|
|
555
|
+
].join('\n');
|
|
556
|
+
return [
|
|
557
|
+
'AWS4-HMAC-SHA256',
|
|
558
|
+
amzDate,
|
|
559
|
+
credentialScope,
|
|
560
|
+
this.sha256(canonicalRequest)
|
|
561
|
+
].join('\n');
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Sign string using AWS Signature V4
|
|
565
|
+
*/
|
|
566
|
+
signString(stringToSign) {
|
|
567
|
+
const dateStamp = this.getAmzDate().substring(0, 8);
|
|
568
|
+
const signingKey = this.getSignatureKey(this.config.secretAccessKey, dateStamp, this.config.region || 'us-east-1', 's3');
|
|
569
|
+
return createHmac('sha256', signingKey)
|
|
570
|
+
.update(stringToSign)
|
|
571
|
+
.digest('hex');
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Get AWS Signature V4 signing key
|
|
575
|
+
*/
|
|
576
|
+
getSignatureKey(key, dateStamp, regionName, serviceName) {
|
|
577
|
+
const kDate = createHmac('sha256', 'AWS4' + key).update(dateStamp).digest();
|
|
578
|
+
const kRegion = createHmac('sha256', kDate).update(regionName).digest();
|
|
579
|
+
const kService = createHmac('sha256', kRegion).update(serviceName).digest();
|
|
580
|
+
const kSigning = createHmac('sha256', kService).update('aws4_request').digest();
|
|
581
|
+
return kSigning;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Calculate SHA256 hash
|
|
585
|
+
*/
|
|
586
|
+
sha256(data) {
|
|
587
|
+
return createHash('sha256').update(data).digest('hex');
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Calculate SHA256 hash of buffer
|
|
591
|
+
*/
|
|
592
|
+
calculateSHA256(buffer) {
|
|
593
|
+
return createHash('sha256').update(buffer).digest('hex');
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Build metadata headers (x-amz-meta-*)
|
|
597
|
+
*/
|
|
598
|
+
buildMetadataHeaders(metadata) {
|
|
599
|
+
const headers = {};
|
|
600
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
601
|
+
headers[`x-amz-meta-${key.toLowerCase()}`] = value;
|
|
602
|
+
}
|
|
603
|
+
return headers;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Extract metadata from response headers
|
|
607
|
+
*/
|
|
608
|
+
extractMetadata(headers) {
|
|
609
|
+
const metadata = {};
|
|
610
|
+
headers.forEach((value, key) => {
|
|
611
|
+
if (key.toLowerCase().startsWith('x-amz-meta-')) {
|
|
612
|
+
const metaKey = key.substring('x-amz-meta-'.length);
|
|
613
|
+
metadata[metaKey] = value;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Parse S3 ListObjectsV2 XML response
|
|
620
|
+
*/
|
|
621
|
+
parseListObjectsResponse(xml) {
|
|
622
|
+
// Simple XML parsing (production should use proper XML parser)
|
|
623
|
+
const objects = [];
|
|
624
|
+
const contentsRegex = /<Contents>(.*?)<\/Contents>/gs;
|
|
625
|
+
const matches = xml.matchAll(contentsRegex);
|
|
626
|
+
for (const match of matches) {
|
|
627
|
+
const content = match[1];
|
|
628
|
+
const key = this.extractXMLValue(content, 'Key');
|
|
629
|
+
const size = parseInt(this.extractXMLValue(content, 'Size') || '0', 10);
|
|
630
|
+
const lastModified = new Date(this.extractXMLValue(content, 'LastModified') || Date.now());
|
|
631
|
+
const etag = this.extractXMLValue(content, 'ETag')?.replace(/"/g, '') || '';
|
|
632
|
+
const storageClass = this.extractXMLValue(content, 'StorageClass');
|
|
633
|
+
if (key) {
|
|
634
|
+
objects.push({
|
|
635
|
+
key,
|
|
636
|
+
size,
|
|
637
|
+
lastModified,
|
|
638
|
+
etag,
|
|
639
|
+
storageClass
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const isTruncated = this.extractXMLValue(xml, 'IsTruncated') === 'true';
|
|
644
|
+
const nextContinuationToken = this.extractXMLValue(xml, 'NextContinuationToken');
|
|
645
|
+
const keyCount = parseInt(this.extractXMLValue(xml, 'KeyCount') || '0', 10);
|
|
646
|
+
return {
|
|
647
|
+
objects,
|
|
648
|
+
isTruncated,
|
|
649
|
+
nextContinuationToken,
|
|
650
|
+
keyCount
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Extract value from XML string
|
|
655
|
+
*/
|
|
656
|
+
extractXMLValue(xml, tag) {
|
|
657
|
+
const regex = new RegExp(`<${tag}>(.*?)<\/${tag}>`, 's');
|
|
658
|
+
const match = xml.match(regex);
|
|
659
|
+
return match ? match[1] : undefined;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Extract upload ID from XML response
|
|
663
|
+
*/
|
|
664
|
+
extractUploadId(xml) {
|
|
665
|
+
const uploadId = this.extractXMLValue(xml, 'UploadId');
|
|
666
|
+
if (!uploadId) {
|
|
667
|
+
throw new Error('Failed to extract UploadId from response');
|
|
668
|
+
}
|
|
669
|
+
return uploadId;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Build CompleteMultipartUpload XML body
|
|
673
|
+
*/
|
|
674
|
+
buildCompleteMultipartUploadXML(parts) {
|
|
675
|
+
const partXMLs = parts
|
|
676
|
+
.map(part => ` <Part>\n <PartNumber>${part.partNumber}</PartNumber>\n <ETag>"${part.etag}"</ETag>\n </Part>`)
|
|
677
|
+
.join('\n');
|
|
678
|
+
return `<CompleteMultipartUpload>\n${partXMLs}\n</CompleteMultipartUpload>`;
|
|
679
|
+
}
|
|
680
|
+
}
|