tomasmcguinness / dotnet-passbook

A .Net Library for generating Apple Passbook (Wallet) files for iOS. Please get involved by creating pull requests and opening issues!
MIT License
319 stars 117 forks source link

Support multiple passes with .pkpasses bundle #153

Closed AverageCakeSlice closed 2 years ago

AverageCakeSlice commented 2 years ago

With iOS 15, Apple now supports bundling multiple tickets together with the new .pkpasses extension and MIME type.

Would it be possible to support this in a future release? I was looking into implementing this in my own app, but I can't figure out how to zip up multiple files without creating some temporary directory to store the generated individual passes.

Any suggestions on how to implement this? I'd be happy to create a PR with this change if we can figure out a good solution!

Cheers!

AverageCakeSlice commented 2 years ago

Just an update, I figured out how to do this successfully using MemoryStream. Here's a sample of the code used:

        // PassFile is just a simple object I made with a Filename as a string and Contents as a byte[] 
        var passes = new List<PassFile>();

        // Build requests here...
        // In a typical scenario you'd probably build your requests in a loop
        var generatedPass = generator.Generate(passRequest);
        var generatedPass2 = generator.Generate(passRequest2);

        passes.Add(new PassFile
        {
            Filename = "ticket1.pkpass",
            Contents = generatedPass,
        });

        passes.Add(new PassFile
        {
            Filename = "ticket2.pkpass",
            Contents = generatedPass2,
        });

        await using var compressedFileStream = new MemoryStream();
        //Create an archive and store the stream in memory.
        using var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, true);
        foreach (var pass in passes) {
            //Create a zip entry for each attachment
            var zipEntry = zipArchive.CreateEntry(pass.Filename);

            //Get the stream of the attachment
            await using var originalFileStream = new MemoryStream(pass.Contents);
            await using var zipEntryStream = zipEntry.Open();
            //Copy the attachment stream to the zip entry stream
            await originalFileStream.CopyToAsync(zipEntryStream);
        }

        zipArchive.Dispose();

        return compressedFileStream.ToArray();

The byte array that gets returned is then just bundled up as a pkpasses file (with a matching MIME type as well), but is otherwise returned the same as a singular pkpass file

        var generatedPass = await _passbookService.GeneratePass(); // Byte array from earlier
        return new FileContentResult(generatedPass, "application/vnd.apple.pkpasses")
        {
            FileDownloadName = "tickets.pkpasses.zip"
        };

@tomasmcguinness I'm not sure how you'd like to implement dynamic naming of files, if at all. Should the hypothetical Generate() overload take in an array of PassFile objects, or perhaps a dictionary?

Thanks in advance!

tomasmcguinness commented 2 years ago

PR merged in! I've tweaked the code to remove the need to send in the files names. The code will generate them automatically. I'll test that later today if I can.

tomasmcguinness commented 2 years ago

Hey, I just tweaked your code a little to remove the need for the pkpass file names when generating a bundle. I just generates names pass0.pkpass to pass(n).pkpass. I felt this was less thinking on the developers side. Can you see any problems with that?

AverageCakeSlice commented 2 years ago

Hey, I just tweaked your code a little to remove the need for the pkpass file names when generating a bundle. I just generates names pass0.pkpass to pass(n).pkpass. I felt this was less thinking on the developers side. Can you see any problems with that?

@tomasmcguinness I think that would work fine; Apple Wallet doesn't seem to care what the file names are anyway. The less manual legwork, the better!