@craft-native/ios 0.0.153 → 0.0.154

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -602,8 +602,12 @@ await fetch('https://example.com/upload', { method: 'POST', body: form });
602
602
  // A file for the share sheet (Save to Files, AirDrop, another app), fetched
603
603
  // straight to disk with your headers. Not 2xx: { ok: false, status, body }.
604
604
  const gpx = await craft.files.download('https://example.com/route.gpx', { headers: { Authorization: 'Bearer …' } });
605
- if (gpx.ok) await craft.share({ files: [gpx.path] });
605
+ if (gpx.ok) await craft.share({ files: [gpx] }); // a file, or its path
606
606
  await craft.files.remove(gpx.path); // or leave it: tmp, swept after a day
607
+ // Something the screen made itself (a CSV export): text crosses the bridge
608
+ // once, as it is; bytes go as base64 ({ encoding: 'base64' }).
609
+ const csv = await craft.files.write('health-data.csv', 'date,steps\n…', { type: 'text/csv' });
610
+ await craft.share({ files: [csv] });
607
611
 
608
612
  // Speech: short spoken cues that duck the person's music instead of stopping
609
613
  // it, and play with the silent switch on. Always available (no config flag).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/ios",
3
- "version": "0.0.153",
3
+ "version": "0.0.154",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/ios#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -171,6 +171,16 @@ enum CraftNativeActions {
171
171
  case ("Files", "download"):
172
172
  download(requestToken: requestToken, args: args, completion: completion)
173
173
  return
174
+ case ("Files", "write"):
175
+ // Something the screen made (a CSV export) as a file for the
176
+ // share sheet; the disk work is off the main thread.
177
+ let name = args.first as? String ?? ""
178
+ let data = args.count > 1 ? args[1] : nil
179
+ let options = args.count > 2 ? args[2] as? [String: Any] ?? [:] : [:]
180
+ DispatchQueue.global(qos: .userInitiated).async {
181
+ complete(requestToken, CraftNativeFiles.write(name: name, data: data, options: options), completion)
182
+ }
183
+ return
174
184
  case ("Files", "remove"):
175
185
  sync = .success(CraftNativeFiles.remove(args.first as? String ?? ""))
176
186
  case ("Lifecycle", "getState"):
@@ -329,6 +329,40 @@ enum CraftNativeFiles {
329
329
  }
330
330
  }
331
331
 
332
+ /// What a screen made itself (a CSV export, a route it built) written as
333
+ /// a file for the share sheet: `data` is text (`encoding: 'utf8'`, the
334
+ /// default, so a CSV crosses the bridge once and no larger) or base64
335
+ /// (`encoding: 'base64'`, for bytes). What is already on a server is
336
+ /// `download`ed instead, straight to disk.
337
+ static func write(name: String, data: Any?, options: [String: Any]) -> Result<Any, CraftNativeActionError> {
338
+ guard !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
339
+ return .failure(CraftNativeActionError(code: "INVALID_ARGUMENT", message: "files.write needs a file name"))
340
+ }
341
+ guard let text = data as? String else {
342
+ return .failure(CraftNativeActionError(code: "INVALID_ARGUMENT", message: "files.write needs its contents as a string"))
343
+ }
344
+ let bytes: Data
345
+ switch options["encoding"] as? String ?? "utf8" {
346
+ case "utf8":
347
+ bytes = Data(text.utf8)
348
+ case "base64":
349
+ guard let decoded = Data(base64Encoded: text.filter { !$0.isWhitespace }) else {
350
+ return .failure(CraftNativeActionError(code: "INVALID_ARGUMENT", message: "files.write was given base64 it could not read"))
351
+ }
352
+ bytes = decoded
353
+ default:
354
+ return .failure(CraftNativeActionError(code: "INVALID_ARGUMENT", message: "files.write encoding is 'utf8' or 'base64'"))
355
+ }
356
+ let type = (options["type"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? "application/octet-stream"
357
+ do {
358
+ let url = try newFile(named: name)
359
+ try bytes.write(to: url, options: .atomic)
360
+ return .success(describe(url, type: type))
361
+ } catch {
362
+ return .failure(CraftNativeActionError(code: "WRITE_ERROR", message: error.localizedDescription))
363
+ }
364
+ }
365
+
332
366
  /// A refused download: its status and the start of its answer, so the
333
367
  /// screen can say what the server said. No file is kept.
334
368
  static func refusedDownload(at location: URL?, response: HTTPURLResponse) -> [String: Any] {
@@ -527,7 +527,11 @@ enum CraftNativeShare {
527
527
  if let title = options["title"] as? String, !title.isEmpty { items.append(title) }
528
528
  if let text = options["text"] as? String, !text.isEmpty { items.append(text) }
529
529
  if let address = options["url"] as? String, let url = URL(string: address) { items.append(url) }
530
- for file in options["files"] as? [String] ?? [] {
530
+ // A path or file URL, or a file craft.files made (`{ path, name }`).
531
+ let files = (options["files"] as? [Any] ?? []).compactMap { item -> String? in
532
+ item as? String ?? (item as? [String: Any])?["path"] as? String
533
+ }
534
+ for file in files {
531
535
  if let url = URL(string: file), url.isFileURL { items.append(url) }
532
536
  else if FileManager.default.fileExists(atPath: file) { items.append(URL(fileURLWithPath: file)) }
533
537
  }