Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Bad blob webhook dev experience #813

Open
riordanpawley opened this issue Dec 23, 2024 · 0 comments
Open

Bad blob webhook dev experience #813

riordanpawley opened this issue Dec 23, 2024 · 0 comments

Comments

@riordanpawley
Copy link

riordanpawley commented Dec 23, 2024

I tried ngrok but it was super laggy and is added complexity for my team.
I think in dev, blob uploadStart should automatically handle calling the api endpoint again on completion or provide some documentation to do so.
E.g.

// client side
export const useVercelBlob: UseVercelBlob = () => {
  const userId = useUserId();
  return async (file, data, onComplete) => {
    const clientPayload = Schema.encodeSync(
      Schema.parseJson(VercelBlobClientToken),
    )(data);
    const response = await upload(file.name, file, {
      clientPayload,
      access: "public",
      handleUploadUrl: "/api/vercel-blob",
    });
    if (env.NEXT_PUBLIC_VERCEL_ENV === "development") {
// must be a string so it gets double stringified
      const tokenPayload: string = Schema.encodeSync(
        Schema.parseJson(VercelBlobPayloadToken),
      )({
        body: data.body,
        userId,
      });
// fake the api request that the webhook should have sent
      const body = {
        type: "blob.upload-completed",
        payload: {
          tokenPayload,
          blob: response,
        },
      };
      await fetch("/api/vercel-blob", {
        method: "POST",
        body: JSON.stringify(body),
        headers: {
          "Content-Type": "application/json",
        },
      });
    }
    onComplete(response);
  };
};
// in api endpoint
  const body = (await request.json()) as HandleUploadBody;
  const signature =
    env.VERCEL_ENV === "development" && body.type === "blob.upload-completed"
      ? await signPayload(
          JSON.stringify(body),
          // biome-ignore lint/style/noNonNullAssertion: <explanation>
          process.env.BLOB_READ_WRITE_TOKEN!,
        )
      : null;
//...
  handleUpload({
      //...
      // pass in the faked signature
      request:
              env.VERCEL_ENV === "development"
                ? ({
                    headers: {
                      "x-vercel-signature": signature,
                    },
                  } as unknown as IncomingMessage)
                : request,
})

// helper functions taken from blob sourcecode
function importKey(token: string): Promise<webcrypto.CryptoKey> {
  return globalThis.crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(token),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign", "verify"],
  );
}
async function signPayload(
  payload: string,
  token: string,
): Promise<string | undefined> {
  const signature = await globalThis.crypto.subtle.sign(
    "HMAC",
    await importKey(token),
    new TextEncoder().encode(payload),
  );
  return Buffer.from(new Uint8Array(signature)).toString("hex");
}

Thoughts?

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant