@storecraft/storage-google
Version:
Official Google Storage adapter for storecraft
292 lines (248 loc) • 5.87 kB
JavaScript
/**
* @import { Config, ServiceFile } from './types.public.js'
* @import { ENV } from '@storecraft/core';
* @import { storage_driver, StorageFeatures } from '@storecraft/core/storage'
*/
import { getJWTFromServiceAccount, presign } from './adapter.utils.js';
const types = {
'png': 'image/png',
'gif': 'image/gif',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'tiff': 'image/tiff',
'webp': 'image/webp',
'txt': 'text/plain',
'json': 'application/json',
}
/**
*
* @param {string} name
*/
const infer_content_type = (name) => {
const idx = name.lastIndexOf('.');
if(!idx) return 'application/octet-stream';
const type = types[name.substring(idx + 1).trim()]
return type ?? 'application/octet-stream';
}
/**
* @description Google Storage adapter
*
* @implements {storage_driver}
*/
export class GoogleStorage {
/** @satisfies {ENV<Config>} */
static EnvConfig = /** @type {const} */ ({
bucket: 'GS_BUCKET',
client_email: 'GS_CLIENT_EMAIL',
private_key: 'GS_PRIVATE_KEY',
private_key_id: 'GS_PRIVATE_KEY_ID',
});
/** @type {Config} */ #_config;
/**
* @param {Config} [config]
*/
constructor(config={}) {
this.#_config = config;
}
get bucket() { return this.config.bucket; }
get config() { return this.#_config; }
/**
* @type {storage_driver["init"]}
*/
async init(app) {
if(!app)
return this;
this.#_config.bucket ??=
app.env[GoogleStorage.EnvConfig.bucket];
this.#_config.client_email ??=
app.env[GoogleStorage.EnvConfig.client_email];
this.#_config.private_key ??=
app.env[GoogleStorage.EnvConfig.private_key];
this.#_config.private_key_id ??=
app.env[GoogleStorage.EnvConfig.private_key_id];
return this;
}
features() {
/** @type {StorageFeatures} */
const f = {
supports_signed_urls: true
}
return f;
}
// puts
/**
* @param {string} key
*/
put_file_url(key) {
const base = 'https://storage.googleapis.com/upload/storage/v1';
return `${base}/b/${this.bucket}/o?uploadType=media&name=${encodeURIComponent(key)}`
}
/**
*
* @param {string} key
* @param {BodyInit} body
*/
async #put_internal(key, body) {
const auth = 'Bearer ' + await getJWTFromServiceAccount(this.config);
const r = await fetch(
this.put_file_url(key),
{
method: 'POST',
body,
headers: {
Authorization: auth,
'Content-Type': 'image/png'
},
// @ts-ignore
duplex: 'half'
}
);
return r.ok;
// console.log(r)
// console.log(JSON.stringify(await r.json(), null, 2))
}
/**
*
* @type {storage_driver["putBlob"]}
*/
async putBlob(key, blob) {
return this.#put_internal(key, blob);
}
/**
*
* @type {storage_driver["putArraybuffer"]}
*/
async putArraybuffer(key, buffer) {
return this.#put_internal(key, buffer);
}
/**
*
* @type {storage_driver["putStream"]}
*/
async putStream(key, stream) {
return this.#put_internal(key, stream);
}
/**
*
* @type {storage_driver["getSigned"]}
*/
async putSigned(key) {
const ct = infer_content_type(key);
const sf = this.config;
const url_signed = await presign({
pem_private_key: sf.private_key,
client_id_email: sf.client_email,
gcs_api_endpoint: 'https://storage.googleapis.com',
path: `/${this.bucket}/${key}`,
verb: 'PUT',
content_md5: '',
content_type: ct
});
return {
url: url_signed,
method: 'PUT',
headers: {
'Content-Type': ct
}
}
}
// gets
/** @param {string} key */
get_file_url(key) {
const base = 'https://storage.googleapis.com/storage/v1';
return `${base}/b/${this.bucket}/o/${encodeURIComponent(key)}`
}
/** @param {string} key */
async #get_request(key) {
const auth = 'Bearer ' + await getJWTFromServiceAccount(this.config);
return fetch(
this.get_file_url(key) + '?alt=media',
{
method: 'GET',
headers: {
Authorization: auth,
}
}
);
}
/**
*
* @type {storage_driver["getArraybuffer"]}
*/
async getArraybuffer(key) {
const r = await this.#get_request(key);
const b = await r.arrayBuffer();
return {
value: b,
metadata: {
contentType: infer_content_type(key)
}
};
}
/**
*
* @type {storage_driver["getBlob"]}
*/
async getBlob(key) {
const r = await this.#get_request(key);
// console.log(await r.json())
// console.log(r)
const b = await r.blob();
return {
value: b,
metadata: {
contentType: infer_content_type(key)
}
};
}
/**
*
* @type {storage_driver["getStream"]}
*/
async getStream(key) {
const s = (await this.#get_request(key)).body
return {
value: s,
metadata: {
contentType: infer_content_type(key)
}
};
}
/**
*
* @type {storage_driver["getSigned"]}
*/
async getSigned(key) {
const sf = this.config;
const url_signed = await presign({
pem_private_key: sf.private_key,
client_id_email: sf.client_email,
gcs_api_endpoint: 'https://storage.googleapis.com',
path: `/${this.bucket}/${key}`,
verb: 'GET',
});
return {
url: url_signed,
method: 'GET',
}
}
// // remove
/**
*
* @type {storage_driver["remove"]}
*/
async remove(key) {
const Authorization = 'Bearer ' + await getJWTFromServiceAccount(this.config);
const r = await fetch(
this.get_file_url(key),
{
method: 'DELETE',
headers: {
Authorization
}
}
);
// console.log(await r.text())
return r.ok;
}
}