Skip to content

Commit

Permalink
Merge pull request #2 from OnedocLabs/dsinghvi/patch-sdk
Browse files Browse the repository at this point in the history
(fix, do not merge): patch sdk for pdf generation
  • Loading branch information
pierredge authored May 23, 2024
2 parents d74779f + 9d217a1 commit 7769f85
Show file tree
Hide file tree
Showing 8 changed files with 4,590 additions and 100 deletions.
1 change: 0 additions & 1 deletion .fernignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Specify files that shouldn't be modified by Fern

src/Client.ts
src/core/form-data-utils/FormDataWrapper.ts
src/core/form-data-utils/index.ts
src/core/index.ts
Expand Down
Binary file added output.pdf
Binary file not shown.
4,442 changes: 4,442 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

31 changes: 21 additions & 10 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export declare namespace FileForgeClient {
timeoutInSeconds?: number;
maxRetries?: number;
}

interface Response{
file?: string;
url?:string;
}
}

export class FileForgeClient {
Expand All @@ -37,20 +42,15 @@ export class FileForgeClient {
files: File[] | fs.ReadStream[],
request: FileForge.GenerateRequest,
requestOptions?: FileForgeClient.RequestOptions
): Promise<stream.Readable> {
): Promise<FileForgeClient.Response>{
const _request = core.newFormData();
const options = await serializers.GenerateRequestOptions.jsonOrThrow(request.options, {
unrecognizedObjectKeys: "passthrough",
allowUnrecognizedUnionMembers: false,
allowUnrecognizedEnumValues: false,
breadcrumbsPrefix: [""],
});
console.log(options);
await _request.append(
"options",
JSON.stringify(options),
{ contentType: "application/json" }
);
await _request.append("options", new Blob([JSON.stringify(options)], { type: "application/json" }));
for (const _file of files) {
await _request.append("files", _file);
}
Expand All @@ -74,10 +74,21 @@ export class FileForgeClient {
timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000,
maxRetries: requestOptions?.maxRetries,
});
console.log("_response", JSON.stringify(_response))

if (_response.ok) {
console.log("_response.body", _response.body);
return _response.body;
const chunks: any[] = [];

for await (let chunk of _response.body) {
chunks.push(chunk);
}

const buffer: Buffer = Buffer.concat(chunks);

if (request.options?.host !== true){
return {"file": buffer.toString()};
}else{
return JSON.parse(buffer.toString())
}
}

if (_response.error.reason === "status-code") {
Expand Down
30 changes: 8 additions & 22 deletions src/core/fetcher/Fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,39 +66,25 @@ async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse
: args.url;

let body: BodyInit | undefined = undefined;
const maybeStringifyBody = (body: any) => {
const maybeStringifyBody = async (body: any) => {
if (body instanceof Uint8Array) {
return body;
} else if (body instanceof FormData
|| body instanceof (await import("formdata-node")).FormData
|| body instanceof (await import("form-data")).default) {
return body as any;
} else {
return JSON.stringify(body);
}
};

if (RUNTIME.type === "node") {
if (args.body instanceof (await import("formdata-node")).FormData) {
// @ts-expect-error
body = args.body;
} else {
body = maybeStringifyBody(args.body);
}
} else {
if (args.body instanceof (await import("form-data")).default) {
// @ts-expect-error
body = args.body;
} else {
body = maybeStringifyBody(args.body);
}
}
body = await maybeStringifyBody(args.body);

// In Node.js environments, the SDK always uses`node-fetch`.
// If not in Node.js the SDK uses global fetch if available,
// and falls back to node-fetch.
const fetchFn =
RUNTIME.type === "node"
? // `.default` is required due to this issue:
// https://github.com/node-fetch/node-fetch/issues/450#issuecomment-387045223
((await import("node-fetch")).default as any)
: typeof fetch == "function"
typeof fetch == "function"
? fetch
: ((await import("node-fetch")).default as any);

Expand Down Expand Up @@ -208,4 +194,4 @@ async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse
}
}

export const fetcher: FetchFunction = fetcherImpl;
export const fetcher: FetchFunction = fetcherImpl;
39 changes: 13 additions & 26 deletions src/core/form-data-utils/FormData.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { RUNTIME } from "../runtime";

export function newFormData(): FormData {
export function newFormData(): CrossRuntimeFormData {
if (RUNTIME.type === "node") {
return new NodeFormData();
} else {
return new WebFormData();
}
}

export declare namespace FormData {
export declare namespace CrossRuntimeFormData {
interface AppendOptions {
contentType?: string;
}
}

export abstract class FormData {
export abstract class CrossRuntimeFormData {
public abstract append(key: string, value: any, options?: { contentType?: string }): Promise<void>;

/**
Expand All @@ -28,48 +28,35 @@ export abstract class FormData {
public abstract getHeaders(): Promise<Record<string, string>>;
}

class NodeFormData implements FormData {
private encoder: any | undefined;
class NodeFormData implements CrossRuntimeFormData {
private fd: any | undefined;

public async initialize(): Promise<void> {
this.fd = new (await import("formdata-node")).FormData();
this.encoder = new (await import("form-data-encoder")).FormDataEncoder(this.fd);
public constructor() {
this.fd = new FormData();
}

public async append(
key: string,
value: any,
options?: { contentType?: string | undefined } | undefined
): Promise<void> {
if (this.fd == null) {
await this.initialize();
}
if (options?.contentType == null) {
this.fd.append(key, value);
if (options?.contentType != null) {
this.fd.append(key, new Blob([value], { type: options?.contentType }));
} else {
this.fd.append(key, new Blob([value], { type: options.contentType }));
this.fd.append(key, value);
}
}

public async getBody(): Promise<any> {
if (this.encoder == null) {
await this.initialize();
}
return (await import("node:stream")).Readable.from(this.encoder);
return this.fd;
}

public async getHeaders(): Promise<Record<string, string>> {
if (this.encoder == null) {
await this.initialize();
}
return {
"Content-Length": this.encoder.length,
};
return {};
}
}

class WebFormData implements FormData {
class WebFormData implements CrossRuntimeFormData {
private fd: any;

public async initialize(): Promise<void> {
Expand Down
147 changes: 106 additions & 41 deletions tests/custom.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
import stream from "stream";
import * as core from "../src/core";
import { FileForgeClient } from "../src";
import * as error from "../src/errors/index";
import fs from "fs";
import { writeFile } from "fs/promises";

const FILEFORGE_API_KEY = process.env.FILEFORGE_API_KEY!;

const HTML = `<!DOCTYPE html>
<html>
<head>
<title>My First Web Page</title>
<link href="style.css" rel="stylesheet" />
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
`;

const CSS =`body{
background-color: lightblue;
}
`;
/**
* This is a custom test file, if you wish to add more tests
* to your SDK.
Expand All @@ -11,45 +32,89 @@ import fs from "fs";
* you will have tests automatically generated for you.
*/
describe("test", () => {
it("should generate a PDF", async () => {
const apiKeySupplier = async () => "ebec0c4c-214f-4796-afd2-b5c9b12281b6"; // Replace with your actual API key supplier
const environmentSupplier = async () => "https://api.fileforge.com"; // Replace with your actual environment endpoint

const client = new FileForgeClient({
apiKey: apiKeySupplier,
environment: environmentSupplier,
});

// Write HTML content to a file
const htmlContent = "<h1>Hello World!</h1>";
const blob = new Blob([htmlContent], { type: "text/html" });
const file = new File([blob], "index.html", { type: "text/html" });

const request = {
options: {
fileName: "test.pdf",
test: false,
host: false,
},
};

const pdfStream: stream.Readable = await client.generate([file], request);
console.log(pdfStream);
const pdfFilePath = "output.pdf";
const writeStream = fs.createWriteStream(pdfFilePath);

pdfStream.pipe(writeStream);

return new Promise((resolve, reject) => {
writeStream.on("finish", () => {
console.log("PDF generated and saved to", pdfFilePath);
resolve(true);
});

writeStream.on("error", (error) => {
console.error("Error generating PDF:", error);
reject(error);
});
});
});
it("should generate a PDF buffer", async () => {
const htmlBlob = new Blob([HTML], {
type: "text/html",
});
const cssBlob = new Blob([CSS], {
type: "text/css",
});
const htmlFile = new File([htmlBlob], "index.html", { type: "text/html" });
const cssFile = new File([cssBlob], "style.css", { type: "text/css" });

const ff = new FileForgeClient({
apiKey: FILEFORGE_API_KEY
});

const pdf:FileForgeClient.Response = await ff.generate(
[htmlFile, cssFile],
{
options: {}
}
);

await writeFile("output.pdf", pdf.file!);
}, 10_000_000);


it("should generate a PDF link", async () => {
const htmlBlob = new Blob([HTML], {
type: "text/html",
});
const cssBlob = new Blob([CSS], {
type: "text/css",
});
const htmlFile = new File([htmlBlob], "index.html", { type: "text/html" });
const cssFile = new File([cssBlob], "style.css", { type: "text/css" });

const ff = new FileForgeClient({
apiKey: FILEFORGE_API_KEY
});

const pdf:FileForgeClient.Response = await ff.generate(
[htmlFile, cssFile],
{
options: {
host: true,
}
}
);

expect(pdf.url).not.toBeNull();

}, 10_000_000);

it("should fail because of invalid api key", async () => {
const htmlBlob = new Blob([HTML], {
type: "text/html",
});
const cssBlob = new Blob([CSS], {
type: "text/css",
});
const htmlFile = new File([htmlBlob], "index.html", { type: "text/html" });
const cssFile = new File([cssBlob], "style.css", { type: "text/css" });

const ff = new FileForgeClient({
apiKey: "blabla_invalid_key"
});
try {
const pdf = await ff.generate(
[htmlFile, cssFile],
{
options: {
host: true,
}
}
);

}catch(e){
expect(e).not.toBeNull();
if (e instanceof error.FileForgeError) {
expect(e.statusCode).toBe(401);
}
}

}, 10_000_000);


});
Empty file added tests/test.html
Empty file.

0 comments on commit 7769f85

Please sign in to comment.