Add headers to request

This commit is contained in:
Kieran 2023-05-22 15:14:22 +01:00
parent 1babc8e4e0
commit e0652dfbb8
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
6 changed files with 50 additions and 34 deletions

View File

@ -1,8 +1,8 @@
{ {
"name": "@void-cat/api", "name": "@void-cat/api",
"version": "1.0.1", "version": "1.0.4",
"description": "void.cat API package", "description": "void.cat API package",
"main": "dist/index.js", "main": "dist/lib.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"repository": "https://git.v0l.io/Kieran/void.cat", "repository": "https://git.v0l.io/Kieran/void.cat",
"author": "Kieran", "author": "Kieran",

View File

@ -52,9 +52,9 @@ export class VoidApi {
*/ */
getUploader( getUploader(
file: File | Blob, file: File | Blob,
stateChange: StateChangeHandler, stateChange?: StateChangeHandler,
progress: ProgressHandler, progress?: ProgressHandler,
proxyChallenge: ProxyChallengeHandler, proxyChallenge?: ProxyChallengeHandler,
chunkSize?: number chunkSize?: number
): VoidUploader { ): VoidUploader {
if (StreamUploader.canUse()) { if (StreamUploader.canUse()) {

View File

@ -27,15 +27,15 @@ export class StreamUploader extends VoidUploader {
return this.#encrypt?.getKey() return this.#encrypt?.getKey()
} }
async upload(): Promise<VoidUploadResult> { async upload(headers?: HeadersInit): Promise<VoidUploadResult> {
this.onStateChange(UploadState.Hashing); this.onStateChange?.(UploadState.Hashing);
const hash = await this.digest(this.file); const hash = await this.digest(this.file);
let offset = 0; let offset = 0;
const DefaultChunkSize = 1024 * 1024; const DefaultChunkSize = 1024 * 1024;
const rsBase = new ReadableStream({ const rsBase = new ReadableStream({
start: async () => { start: async () => {
this.onStateChange(UploadState.Uploading); this.onStateChange?.(UploadState.Uploading);
}, },
pull: async (controller) => { pull: async (controller) => {
const chunk = await this.readChunk(offset, controller.desiredSize ?? DefaultChunkSize); const chunk = await this.readChunk(offset, controller.desiredSize ?? DefaultChunkSize);
@ -43,7 +43,7 @@ export class StreamUploader extends VoidUploader {
controller.close(); controller.close();
return; return;
} }
this.onProgress(offset + chunk.byteLength); this.onProgress?.(offset + chunk.byteLength);
offset += chunk.byteLength offset += chunk.byteLength
controller.enqueue(chunk); controller.enqueue(chunk);
}, },
@ -55,23 +55,26 @@ export class StreamUploader extends VoidUploader {
highWaterMark: DefaultChunkSize highWaterMark: DefaultChunkSize
}); });
const headers = { const reqHeaders = {
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
"V-Content-Type": !this.file.type ? "application/octet-stream" : this.file.type, "V-Content-Type": !this.file.type ? "application/octet-stream" : this.file.type,
"V-Filename": "name" in this.file ? this.file.name : "", "V-Filename": "name" in this.file ? this.file.name : "",
"V-Full-Digest": hash "V-Full-Digest": hash,
} as Record<string, string>; } as Record<string, string>;
if (this.#encrypt) { if (this.#encrypt) {
headers["V-EncryptionParams"] = JSON.stringify(this.#encrypt!.getParams()); reqHeaders["V-EncryptionParams"] = JSON.stringify(this.#encrypt!.getParams());
} }
if (this.auth) { if (this.auth) {
headers["Authorization"] = `Bearer ${this.auth}`; reqHeaders["Authorization"] = `Bearer ${this.auth}`;
} }
const req = await fetch(`${this.uri}/upload`, { const req = await fetch(`${this.uri}/upload`, {
method: "POST", method: "POST",
mode: "cors", mode: "cors",
body: this.#encrypt ? rsBase.pipeThrough(this.#encrypt!.getEncryptionTransform()) : rsBase, body: this.#encrypt ? rsBase.pipeThrough(this.#encrypt!.getEncryptionTransform()) : rsBase,
headers, headers: {
...reqHeaders,
...headers
},
// @ts-ignore New stream spec // @ts-ignore New stream spec
duplex: 'half' duplex: 'half'
}); });

View File

@ -27,16 +27,16 @@ export abstract class VoidUploader {
protected file: File | Blob; protected file: File | Blob;
protected auth?: string; protected auth?: string;
protected maxChunkSize: number; protected maxChunkSize: number;
protected onStateChange: StateChangeHandler; protected onStateChange?: StateChangeHandler;
protected onProgress: ProgressHandler; protected onProgress?: ProgressHandler;
protected onProxyChallenge: ProxyChallengeHandler; protected onProxyChallenge?: ProxyChallengeHandler;
constructor( constructor(
uri: string, uri: string,
file: File | Blob, file: File | Blob,
stateChange: StateChangeHandler, stateChange?: StateChangeHandler,
progress: ProgressHandler, progress?: ProgressHandler,
proxyChallenge: ProxyChallengeHandler, proxyChallenge?: ProxyChallengeHandler,
auth?: string, auth?: string,
chunkSize?: number chunkSize?: number
) { ) {
@ -65,12 +65,16 @@ export abstract class VoidUploader {
const slice = file.slice(offset, offset + ChunkSize); const slice = file.slice(offset, offset + ChunkSize);
const chunk = await slice.arrayBuffer(); const chunk = await slice.arrayBuffer();
sha.update(sjclcodec.toBits(new Uint8Array(chunk))); sha.update(sjclcodec.toBits(new Uint8Array(chunk)));
this.onProgress(progress += chunk.byteLength); this.onProgress?.(progress += chunk.byteLength);
} }
return buf2hex(sjclcodec.fromBits(sha.finalize())); return buf2hex(sjclcodec.fromBits(sha.finalize()));
} }
abstract upload(): Promise<VoidUploadResult>; /**
* Upload a file to the API
* @param headers any additional headers to send with the request
*/
abstract upload(headers?: HeadersInit): Promise<VoidUploadResult>;
/** /**
* Can we use local encryption * Can we use local encryption

View File

@ -14,23 +14,23 @@ export class XHRUploader extends VoidUploader {
return undefined; return undefined;
} }
async upload(): Promise<VoidUploadResult> { async upload(headers?: HeadersInit): Promise<VoidUploadResult> {
this.onStateChange(UploadState.Hashing); this.onStateChange?.(UploadState.Hashing);
const hash = await this.digest(this.file); const hash = await this.digest(this.file);
if (this.file.size > this.maxChunkSize) { if (this.file.size > this.maxChunkSize) {
return await this.#doSplitXHRUpload(hash, this.maxChunkSize); return await this.#doSplitXHRUpload(hash, this.maxChunkSize, headers);
} else { } else {
return await this.#xhrSegment(this.file, hash); return await this.#xhrSegment(this.file, hash, undefined, undefined, 1, 1, headers);
} }
} }
async #doSplitXHRUpload(hash: string, splitSize: number) { async #doSplitXHRUpload(hash: string, splitSize: number, headers?: HeadersInit) {
let xhr: VoidUploadResult | null = null; let xhr: VoidUploadResult | null = null;
const segments = Math.ceil(this.file.size / splitSize); const segments = Math.ceil(this.file.size / splitSize);
for (let s = 0; s < segments; s++) { for (let s = 0; s < segments; s++) {
const offset = s * splitSize; const offset = s * splitSize;
const slice = this.file.slice(offset, offset + splitSize, this.file.type); const slice = this.file.slice(offset, offset + splitSize, this.file.type);
xhr = await this.#xhrSegment(slice, hash, xhr?.file?.id, xhr?.file?.metadata?.editSecret, s + 1, segments); xhr = await this.#xhrSegment(slice, hash, xhr?.file?.id, xhr?.file?.metadata?.editSecret, s + 1, segments, headers);
if (!xhr.ok) { if (!xhr.ok) {
break; break;
} }
@ -46,9 +46,11 @@ export class XHRUploader extends VoidUploader {
* @param editSecret * @param editSecret
* @param part Segment number * @param part Segment number
* @param partOf Total number of segments * @param partOf Total number of segments
* @param headers
*/ */
async #xhrSegment(segment: ArrayBuffer | Blob, fullDigest: string, id?: string, editSecret?: string, part?: number, partOf?: number) { async #xhrSegment(segment: ArrayBuffer | Blob, fullDigest: string,
this.onStateChange(UploadState.Uploading); id?: string, editSecret?: string, part?: number, partOf?: number, headers?: HeadersInit) {
this.onStateChange?.(UploadState.Uploading);
return await new Promise<VoidUploadResult>((resolve, reject) => { return await new Promise<VoidUploadResult>((resolve, reject) => {
try { try {
@ -60,15 +62,15 @@ export class XHRUploader extends VoidUploader {
} else if (req.readyState === XMLHttpRequest.DONE && req.status === 403) { } else if (req.readyState === XMLHttpRequest.DONE && req.status === 403) {
const contentType = req.getResponseHeader("content-type"); const contentType = req.getResponseHeader("content-type");
if (contentType?.toLowerCase().trim().indexOf("text/html") === 0) { if (contentType?.toLowerCase().trim().indexOf("text/html") === 0) {
this.onProxyChallenge(req.response); this.onProxyChallenge?.(req.response);
this.onStateChange(UploadState.Challenge); this.onStateChange?.(UploadState.Challenge);
reject(new Error("CF Challenge")); reject(new Error("CF Challenge"));
} }
} }
}; };
req.upload.onprogress = (e) => { req.upload.onprogress = (e) => {
if (e instanceof ProgressEvent) { if (e instanceof ProgressEvent) {
this.onProgress(e.loaded); this.onProgress?.(e.loaded);
} }
}; };
req.open("POST", id ? `${this.uri}/upload/${id}` : `${this.uri}/upload`); req.open("POST", id ? `${this.uri}/upload/${id}` : `${this.uri}/upload`);
@ -84,6 +86,11 @@ export class XHRUploader extends VoidUploader {
if (editSecret) { if (editSecret) {
req.setRequestHeader("V-EditSecret", editSecret); req.setRequestHeader("V-EditSecret", editSecret);
} }
if (headers) {
for (const [k, v] of Object.entries(headers)) {
req.setRequestHeader(k, v);
}
}
req.send(segment); req.send(segment);
} catch (e) { } catch (e) {
reject(e); reject(e);

View File

@ -3,7 +3,9 @@
const path = require('path'); const path = require('path');
const isProduction = process.env.NODE_ENV == 'production'; const isProduction = process.env.NODE_ENV == 'production';
const config = { const config = {
entry: './src/index.ts', entry: {
lib: './src/index.ts',
},
devtool: isProduction ? "source-map" : "eval", devtool: isProduction ? "source-map" : "eval",
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),