alexandercerutti / passkit-generator

The easiest way to generate custom Apple Wallet passes in Node.js
MIT License
829 stars 104 forks source link

addBuffer not working for remote images. #187

Closed StephanWalters closed 8 months ago

StephanWalters commented 8 months ago

Running OS

Environment: Firebase Cloud Functions Node.js 18 functions.

Running Node Version 18

Description

When trying to use addBuffer to dynamically add an image to the pass. It doesn't add, but no error is thrown. I am using node-fetch to fetch the image and then converting as an ArrayBuffer.

Expected behavior

Expecting the background image to be added to the pass.

Steps to reproduce

Code:

const productImage = await nodeFetch("https image url");
const productImageBuffer = await sellerImage.arrayBuffer();
newPass.addBuffer("background.png", productImageBuffer);
newPass.addBuffer("background@2x.png", productImageBuffer);

Were you able to verify it by using (and changing) the examples?

If yes, which changes did you apply?

Other details

alexandercerutti commented 8 months ago

Hi @StephanWalters! Thanks for using passkit-generator!

addBuffer, as the name says, accepts a NodeJS Buffer, which extends (or shares properties with) UInt8Array.

An ArrayBuffer is just a raw data representation, without a specific meaning (e.g. Big Endian or Little Endian notation or byte size for a single cell). To be read, it requires a View.

UInt8Array is one of the TypedArrays belonging to the Web standard that compose and, as it is a TypedArray, it can act as a View for your ArrayBuffer.

An ArrayBuffer doesn't own a property length, but instead a byteLength (I think the reason is exactly this one, to avoid confusion), so it stops right here:

https://github.com/alexandercerutti/passkit-generator/blob/bab4117cb029b00a50077262187fcc2ad9412357/src/PKPass.ts#L412-L417

So, my guess here is that it should work if you do something like:

const productImageAB = await nodeFetch("https image URL").then(res => res.arrayBuffer());
const productImage = Buffer.from(productImageAB);

newPass.addBuffer("name", productImage);

Could you please try and let me know?

Thank you!

StephanWalters commented 8 months ago

@alexandercerutti thank you for the quick reply!

I tried but am still receiving no error and no image in the pass. I am also seeing this warning. But i am also getting a weird user experience. Im currently testing my implementation firing a firebase url request that returns the pass. However, changing to use Buffer.from() returns a 200, but i don't get a pass. Instead Safari (on iPhone) just reloads whatever webpage was on screen prior to me executing my function.

Argument type Buffer is not assignable to parameter type Buffer.

I am not sure if this makes a difference, but i am also using PKPass.from (with a small pass.json) instead of the constructor PKPass(). I am doing this because i ran out of memory with my cloud function do using multiple buffers for the first dictionary used in PKPass().

      const sellerImage = await nodeFetch(sellerData.photo);
      const productImage = await nodeFetch(productPhoto);

      const sellerImageBuffer = await sellerImage.arrayBuffer();
      const productImageBuffer = await productImage.arrayBuffer();
      const sellerfromBuffer = Buffer.from(sellerImageBuffer);
      const productfromBuffer = Buffer.from(productImageBuffer);

      logger.debug(`sellerData.photo: ${sellerData.photo}`);
      logger.debug(`product photo: ${productPhoto}`);

      newPass.addBuffer("thumbnail@1x.png",
          sellerfromBuffer);
      newPass.addBuffer("thumbnail@2x.png",
          sellerfromBuffer);

      newPass.addBuffer("background.png",
          productfromBuffer);
      newPass.addBuffer("background@2x.png",
          productfromBuffer);
alexandercerutti commented 8 months ago

May I ask you why you're still using node-fetch, when starting from Node 17, fetch is available globally? I see you are running on Node 18. May I ask you to use it to be sure it is specifically compliant with the standard? I don't think this will solve it, but who knows...

Argument type Buffer is not assignable to parameter type Buffer.

I'm not sure where this happens exactly. Do you have any further details?

I tried but am still receiving no error and no image in the pass. I am also seeing this warning. But i am also getting a weird user experience. Im currently testing my implementation firing a firebase url request that returns the pass. However, changing to use Buffer.from() returns a 200, but i don't get a pass. Instead Safari (on iPhone) just reloads whatever webpage was on screen prior to me executing my function.

That's kind of weird. Do you have perhaps a repro case that I could use to investigate?

StephanWalters commented 8 months ago

@alexandercerutti no particular reason. I'm primary is a front end mobile dev (iOS) and self taught myself javascript/Node.js.

As far as: Argument type Buffer is not assignable to parameter type Buffer. it seemed to be a type missmatch importing node buffer cleared the warning. const {Buffer} = require("node:buffer");

This is index.js firebase onRequest function. I removed a few strings and replaced with HIDDEN, but this is pretty much my exact function. Removing the newPass.addBuffer() will recreate the pass, but removing it wont. Something else, that i see that's odd is with this code (on desktop) it creates a pkpasses bundle, which is 14.4MB (thats crazy large) without the newPass.addBuffer() it will work on safari web and mobile but the pass is much smaller.

const {onRequest} = require("firebase-functions/v2/https");

REDACTED

The TL;DR: I am passing the orderId as a query parameter to get an order from the database. there is an items array that has a list of item objects (purchases) I am looping through those to create product passes and packing them together and returning as a stream.

alexandercerutti commented 8 months ago

Something else, that i see that's odd is with this code (on desktop) it creates a pkpasses bundle, which is 14.4MB (thats crazy large) without the newPass.addBuffer() it will work on safari web and mobile but the pass is much smaller.

It depends on your assets. I do not apply any compression, but I just concatenate the buffers. I made available a "hook" (.getAsRaw()) to let people apply their compression and serve the compiled pass. I don't know how many pkpass are you zipping inside your .pkpasses zip file, so 14MB might be reasonable based on the sizes of your assets.

By the way, please note that .pkpasses that you are trying to output are supported only by Safari (and Mail) and I think only on iPhone; I'm not sure they are supported on Desktop.


About your code, I can suggest you one or two things:

1) Move the reading of the certificates outside the loop. You only need them once. 2) Try to build only one .pkpass and check if there's anything different. 3) Try following the guide for debugging that I set up in the wiki and check if, when downloading the pass, some weird error pops on. Apple logs and errors for Apple Wallet are not documented at all, so if you find something weird, I please you to report it here, so it can be useful to other developers in the future.

StephanWalters commented 8 months ago

@alexandercerutti i am closing this issue due to human error. I was creating a coupon type pass and the image needed was strip.png. Sorry about that

alexandercerutti commented 8 months ago

Don't worry, @StephanWalters! Glad you solved!

It would be awesome if you could leave a 🌟 on the project!

StephanWalters commented 8 months ago

Don't worry, @StephanWalters! Glad you solved!

It would be awesome if you could leave a 🌟 on the project!

just did! thank you!