square / square-nodejs-sdk

Other
88 stars 40 forks source link

createCatalogImage is unable to use native File type? #141

Closed ozzyonfire closed 8 months ago

ozzyonfire commented 8 months ago

Describe the bug The createCatalogImage call does not accept a File type. Getting an error; source.on is not a function.

Expected behavior The api should be able to handle the native File type from node as it is a subset of Blob.

To Reproduce Steps to reproduce the bug:

Create a form on your client:


    const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault();
        const formData = new FormData(event.currentTarget);
        const response = await fetch('/api/upload', {
            method: 'POST',
            body: formData,
        });
        const json = await response.json();
        console.log(json);
    }

    return (
        <form onSubmit={handleSubmit} className="flex flex-col gap-2" encType="multipart/form-data">
            <input type="hidden" name="id" value={props.id} />
            <label htmlFor="image">
                <span className="text-xl font-bold text-gray-900">Image</span>
                <input type="file" name="image" required />
            </label>
            <button className="rounded border border-gray-200 px-4 py-1" type="submit">Upload</button>
        </form>
    )

Server side create the image

import client from "@/lib/square";
import { FileWrapper } from "square";

export const POST = async (request: Request) => {
    // form submission to upload photo
    const body = await request.formData();

    console.log(request.headers.get("Content-Type"));

    const { image, id } = Object.fromEntries(body.entries()) as {
        image: File;
        id: string;
    };

    const fileWrapper = new FileWrapper(image, {
        contentType: image.type,
        filename: image.name,

    });

    try {
        const { result, request, body } = await client.catalogApi.createCatalogImage({
            objectId: id.toString(),
            isPrimary: true,
            idempotencyKey: id.toString(),
            image: {
                id: `#${id}`,
                type: "IMAGE",
                imageData: {
                    caption: "test",
                    name: image.name,
                },
            }
        }, fileWrapper);
        console.log(result);
    } catch (err) {
        console.error(err);
        const error = err as Error;
    }

    return new Response("ok");
}

Screenshots

image

Square SDK version ^33.1.0

Additional context It seems like the work around could be to save the file to disk and then create a readstream from there, but with the advent of serverless frameworks (and moving to more native js apis) I would expect this flow to work.

ozzyonfire commented 8 months ago

And I figured it out.... was just missing a step of converting the file to a Readable stream.

new backend code;

import client from "@/lib/square";
import { FileWrapper } from "square";
import { Readable } from "stream";

export const POST = async (request: Request) => {
    // form submission to upload photo
    const body = await request.formData();
    const { image, id } = Object.fromEntries(body.entries()) as {
        image: File;
        id: string;
    };

    // convert image to Readable stream
    const bytes = await image.arrayBuffer();
    const buffer = Buffer.from(bytes);
    const readable = new Readable();
    readable.push(buffer);
    readable.push(null);

    const fileWrapper = new FileWrapper(readable, {
        contentType: image.type,
        filename: image.name,
    });

    try {
        const { result } = await client.catalogApi.createCatalogImage({
            objectId: id.toString(),
            isPrimary: true,
            idempotencyKey: id.toString(),
            image: {
                id: `#${id}`,
                type: "IMAGE",
                imageData: {
                    caption: "test",
                    name: image.name,
                },
            }
        }, fileWrapper);
        console.log(result);
    } catch (err) {
        console.error(err);
        const error = err as Error;
    }

    return new Response("ok");
}

Hopefully this helps someone else. There could be a case where the package itself could just accept the File type and save us a step, so I will leave it open. However if the above is the recommended method maybe we could consider adding it t othe docs?

zenmasterjobo commented 8 months ago

Hey @ozzyonfire - sorry for the pain point here, but yes converting image file to a readable stream is the correct course of action here. I am looking at our docs though, and am seeing what you are saying that we don't seem to have that defined anywhere in the reference or guides. The sample code usually shows converting it to a readable stream, but we should be more clear on that. Thank you for your contributions here, and I'll take this feedback to the relevant team to get this addressed. Thanks!