-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
81d84b3
commit 6faab7a
Showing
12 changed files
with
305 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { DownloadOptions, StorageConstructorOptions } from './types' | ||
import downloadJsonFromS3 from './downloadJsonFromS3' | ||
import { SubmissionTypes } from '@oneblink/types' | ||
/** | ||
* Used to create an instance of the OneBlinkDownloader, exposing methods to | ||
* download submissions and other types of files | ||
*/ | ||
export default class OneBlinkDownloader { | ||
apiOrigin: StorageConstructorOptions['apiOrigin'] | ||
region: StorageConstructorOptions['region'] | ||
getBearerToken: StorageConstructorOptions['getBearerToken'] | ||
|
||
/** | ||
* #### Example | ||
* | ||
* ```typescript | ||
* import { OneBlinkDownloader } from '@oneblink/storage' | ||
* | ||
* const downloader = new OneBlinkDownloader({ | ||
* apiOrigin: 'https://auth-api.blinkm.io', | ||
* region: 'ap-southeast-2', | ||
* getBearerToken: () => getAccessToken(), | ||
* }) | ||
* ``` | ||
*/ | ||
constructor(props: StorageConstructorOptions) { | ||
this.apiOrigin = props.apiOrigin | ||
this.region = props.region | ||
this.getBearerToken = props.getBearerToken | ||
} | ||
|
||
/** | ||
* Download a form submission. | ||
* | ||
* #### Example | ||
* | ||
* ```ts | ||
* const result = await downloader.downloadSubmission({ | ||
* submissionId: '5ad46e62-f466-451c-8cd6-29ba23ac50b7', | ||
* formId: 1, | ||
* abortSignal: new AbortController().signal, | ||
* }) | ||
* ``` | ||
* | ||
* @param data The submission upload data and options | ||
* @returns The submission | ||
*/ | ||
async downloadSubmission({ | ||
submissionId, | ||
formId, | ||
abortSignal, | ||
}: DownloadOptions & { | ||
/** The identifier of the submission. */ | ||
submissionId: string | ||
/** The identifier of the form associated with the submission. */ | ||
formId: number | ||
}) { | ||
return await downloadJsonFromS3<SubmissionTypes.S3SubmissionData>({ | ||
...this, | ||
key: `forms/${formId}/submissions/${submissionId}`, | ||
abortSignal, | ||
}) | ||
} | ||
|
||
/** | ||
* Download a draft form submission. | ||
* | ||
* #### Example | ||
* | ||
* ```ts | ||
* const result = await downloader.downloadDraftSubmission({ | ||
* formSubmissionDraftVersionId: '5ad46e62-f466-451c-8cd6-29ba23ac50b7', | ||
* formId: 1, | ||
* abortSignal: new AbortController().signal, | ||
* }) | ||
* ``` | ||
* | ||
* @param data The submission upload data and options | ||
* @returns The submission | ||
*/ | ||
async downloadDraftSubmission({ | ||
formSubmissionDraftVersionId, | ||
formId, | ||
abortSignal, | ||
}: DownloadOptions & { | ||
/** The identifier of the draft form submission version. */ | ||
formSubmissionDraftVersionId: string | ||
/** The identifier of the form associated with the draft submission. */ | ||
formId: number | ||
}) { | ||
return await downloadJsonFromS3<SubmissionTypes.S3SubmissionData>({ | ||
...this, | ||
key: `forms/${formId}/drafts/${formSubmissionDraftVersionId}`, | ||
abortSignal, | ||
}) | ||
} | ||
|
||
/** | ||
* Download pre-fill form submission data. | ||
* | ||
* #### Example | ||
* | ||
* ```ts | ||
* const result = await downloader.downloadPrefillData({ | ||
* preFillFormDataId: '5ad46e62-f466-451c-8cd6-29ba23ac50b7', | ||
* formId: 1, | ||
* abortSignal: new AbortController().signal, | ||
* }) | ||
* ``` | ||
* | ||
* @param data The submission upload data and options | ||
* @returns The submission | ||
*/ | ||
async downloadPrefillData<T extends SubmissionTypes.S3SubmissionData>({ | ||
preFillFormDataId, | ||
formId, | ||
abortSignal, | ||
}: DownloadOptions & { | ||
/** The identifier of the pre-fill data. */ | ||
preFillFormDataId: string | ||
/** The identifier of the form associated with the pre-fill data. */ | ||
formId: number | ||
}) { | ||
return await downloadJsonFromS3<T>({ | ||
...this, | ||
key: `forms/${formId}/pre-fill/${preFillFormDataId}`, | ||
abortSignal, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { GetObjectCommand, GetObjectCommandOutput } from '@aws-sdk/client-s3' | ||
import { DownloadOptions, StorageConstructorOptions } from './types' | ||
import { generateS3Client } from './generateS3Client' | ||
import OneBlinkStorageError from './OneBlinkStorageError' | ||
|
||
export default async function downloadJsonFromS3<T>({ | ||
key, | ||
abortSignal, | ||
...storageConstructorOptions | ||
}: DownloadOptions & | ||
StorageConstructorOptions & { | ||
key: string | ||
}): Promise<T | undefined> { | ||
const { s3Client, bucket, oneBlinkRequestHandler } = generateS3Client({ | ||
...storageConstructorOptions, | ||
requestBodyHeader: undefined, | ||
}) | ||
|
||
try { | ||
const getObjectCommandOutput = | ||
await oneBlinkRequestHandler.sendS3Command<GetObjectCommandOutput>( | ||
async () => | ||
await s3Client.send( | ||
new GetObjectCommand({ | ||
Bucket: bucket, | ||
Key: key, | ||
}), | ||
{ | ||
abortSignal, | ||
}, | ||
), | ||
) | ||
|
||
return oneBlinkRequestHandler.oneBlinkHttpHandler.parseGetObjectCommandOutputAsJson<T>( | ||
getObjectCommandOutput, | ||
) | ||
} catch (error) { | ||
if (error instanceof OneBlinkStorageError && error.httpStatusCode === 403) { | ||
return | ||
} else { | ||
throw error | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { S3Client } from '@aws-sdk/client-s3' | ||
import { StorageConstructorOptions } from './types' | ||
import { getOneBlinkHttpHandler } from './http-handlers' | ||
import { OneBlinkRequestHandler } from './OneBlinkRequestHandler' | ||
import { RequestBodyHeader } from './http-handlers/types' | ||
|
||
const RETRY_ATTEMPTS = 3 | ||
|
||
export function generateS3Client<T>({ | ||
region, | ||
apiOrigin, | ||
getBearerToken, | ||
requestBodyHeader, | ||
}: StorageConstructorOptions & { | ||
requestBodyHeader?: RequestBodyHeader | ||
}) { | ||
const oneBlinkHttpHandler = getOneBlinkHttpHandler() | ||
const oneBlinkRequestHandler = new OneBlinkRequestHandler<T>( | ||
oneBlinkHttpHandler, | ||
requestBodyHeader, | ||
) | ||
|
||
// The endpoint we use instead of the the AWS S3 endpoint is | ||
// formatted internally by the AWS S3 SDK. It will add the Bucket | ||
// parameter below as the subdomain to the URL (as long as the | ||
// bucket does not contain a `.`). The logic below allows the final | ||
// URL used to upload the object to be the origin that is passed in. | ||
// The suffix on the end is important as it will allow us to route | ||
// traffic to S3 via lambda at edge instead of going to our API. | ||
const url = new URL(apiOrigin) | ||
url.pathname = '/storage' | ||
const [bucket, ...domainParts] = url.hostname.split('.') | ||
url.hostname = domainParts.join('.') | ||
|
||
return { | ||
bucket, | ||
oneBlinkRequestHandler, | ||
s3Client: new S3Client({ | ||
endpoint: url.href, | ||
region, | ||
maxAttempts: RETRY_ATTEMPTS, | ||
requestHandler: oneBlinkRequestHandler, | ||
// Sign requests with our own Authorization header instead | ||
// of letting AWS SDK attempt to generate credentials | ||
signer: { | ||
sign: async (request) => { | ||
const token = await getBearerToken() | ||
if (token) { | ||
request.headers['authorization'] = 'Bearer ' + token | ||
} | ||
|
||
return request | ||
}, | ||
}, | ||
}), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.