2

I want to use server-to-server cloudkit js. to save record with Asset field.
the Asset field is a m4a audio. after saved, the audio file is corrupt to play

The Apple's Doc is not clear about the Asset field.
In a record that is being saved to the database, the value of an Asset field must be a window.Blob type. In the code fragment above, the type of the assetFile variable is window.File.
Docs: https://developer.apple.com/documentation/cloudkitjs/cloudkit/database/1628735-saverecords

but in nodejs ,there is no Blob or .File, I filled it with a buffer like this code:


var dstFile = path.join(__dirname,"../test.m4a");
var data = fs.readFileSync(dstFile);
let buffer = Buffer.from(data);

var rec = {
    recordType: "MyAttachment",
    fields: {
      ext: { value: ".m4a" },
      file: { value: buffer }
    }
  }
  //console.debug(rec);
  mydatabase.newRecordsBatch().create(rec).commit().then(function (response) {
    if (response.hasErrors) {
      console.log(">>> saveAttachFile record failed");
      console.warn(response.errors[0]);

    } else {
      var createdRecord = response.records[0];
      console.log(">>> saveAttachFile record success:", createdRecord);
    }
  });

The record is successful be saved. success But when I download the audio from icloud.developer.apple.com/dashboard .
the audio file is corrupt to play.
What's wrong with it. thank you to reply.

Haozes
  • 320
  • 2
  • 9

1 Answers1

2

I was having the same problem and have found a working solution!

Remembering that CloudKitJS needs you to define your own fetch method, I implemented a custom one to see what was going on. I then attached a debugger on the custom fetch to inspect the data that was passing through it.

After stepping through the caller, I found that all asset values are transformed using its toString() method only when the library is embedded in NodeJS. This is determined by the absence of the global window object.

When toString() is called on a Buffer, its contents are encoded to UTF-8 (by default), which causes binary assets to become malformed. If you're using node-fetch for your fetch implementation, it supports Buffer and stream.Readable, so this toString() call does nothing but harm.

The most unobtrusive fix I've found is to swap the toString() method on any Buffer or stream.Readable instances passed as an asset field values. You should probably use stream.Readable, by the way, so that you don't load the entire asset in memory when uploading.

Anyway, here's what it looks like in practice:

// Put this somewhere in your implementation
const swizzleBuffer = (buffer) => {
    buffer.toString = () => buffer;
    return buffer;
};

// Use this asset value instead
{ asset: swizzleBuffer(fs.readFileSync(path)) }

Please be aware that this workaround mutates a Buffer in an ugly way (since Buffer apparently can't be extended). It's probably a good idea to design an API which doesn't use Buffer arguments so that you can mutate instances that only you create yourself to avoid unintended side effects anywhere else in your code.

Also, sure to vendor (make a local copy) of CloudKitJS in your project, as the behavior may change in the future.

ORIGINAL ANSWER

I ran into the same problem and solved it by encoding my data using Base64. It appears that there's a bug in their SDK which mangles Buffer instances containing non-ascii characters (which, um, seems problematic).

Anyway, try something like this:

const assetField = { value: Buffer.from(data.toString('base64')), 'ascii') }

Side note:

You'll need to decode the asset(s) on the device before using them. There's no way to do this efficiently without writing your own routines, as the methods included in Data / NSData instances requires all data to be in memory.

This is a problem with CloudKitJS (and not the native CloudKit client / service), so the other option is to write your own routine to upload assets.

Neither of these options seem particularly great, but rolling your own atleast means there aren't extra steps for clients to take in order to use the asset.

Mikey
  • 1,282
  • 10
  • 11
  • 1
    Thank you for your answer, I can not verify your answer now, but it sounds great. so I accept it. other friend can check it. – Haozes Nov 13 '19 at 03:41
  • Thank you Mikey, very helpful. Your original answer did the trick for me, which was compressing a string with zlib in base64 and sending it to CK as an asset. Doing it with the swizzler function wasn't needed for me. – DavidD Nov 21 '19 at 10:26