tauri-plugin-icloud-container-api 0.1.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TensorBinge
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # tauri-plugin-icloud-container
2
+
3
+ > ⚠️ This plugin is under active development. Expect API refinements, behavior fixes, and documentation updates before a stable `1.0` release.
4
+
5
+ Tauri v2 plugin for accessing an iCloud ubiquity container from a Tauri app.
6
+
7
+ This plugin provides container identity, coordinated file I/O, directory operations, sync status, download and eviction, and native watch events for iCloud-backed files.
8
+
9
+ Current support: iOS. Desktop targets are not implemented yet.
10
+
11
+ ## Features
12
+
13
+ - Resolve iCloud container availability and URL
14
+ - Read and write UTF-8 or byte content
15
+ - Create files and directories with optional file protection
16
+ - List, copy, move, trash, and delete container items
17
+ - Inspect sync status, trigger download, and evict downloaded items
18
+ - Watch directories and files through native iCloud-backed event sources
19
+ - Typed guest JS API for all commands
20
+
21
+ ## Installation
22
+
23
+ Add the Rust crate to your Tauri app and register the plugin.
24
+
25
+ ```toml
26
+ [dependencies]
27
+ tauri-plugin-icloud-container = "0.1"
28
+ ```
29
+
30
+ ```rust
31
+ fn main() {
32
+ tauri::Builder::default()
33
+ .plugin(tauri_plugin_icloud_container::init())
34
+ .run(tauri::generate_context!())
35
+ .expect("error while running tauri application");
36
+ }
37
+ ```
38
+
39
+ The plugin also exposes a typed guest API for frontend usage.
40
+
41
+ If you want to pin a specific ubiquity container identifier at startup, register the plugin with config instead of calling `init()`.
42
+
43
+ ```rust
44
+ fn main() {
45
+ tauri::Builder::default()
46
+ .plugin(tauri_plugin_icloud_container::init_with_config(
47
+ tauri_plugin_icloud_container::IcloudContainerConfig {
48
+ identifier: Some("iCloud.com.example.app".into()),
49
+ },
50
+ ))
51
+ .run(tauri::generate_context!())
52
+ .expect("error while running tauri application");
53
+ }
54
+ ```
55
+
56
+ When `identifier` is omitted, the native resolver falls back to the first entitled iCloud container available to the host app.
57
+
58
+ ## iOS Setup
59
+
60
+ The host app must be configured for iCloud before any command will succeed.
61
+
62
+ 1. Enable the iCloud capability for the iOS target in Xcode.
63
+ 2. Add the target ubiquity container identifier in the Apple Developer portal and Xcode signing configuration.
64
+ 3. Ensure the app entitlements include the container identifiers needed by the app.
65
+ 4. Build and run on a device or runtime signed into iCloud.
66
+
67
+ If entitlements are missing or the user is signed out, container resolution will fail and the plugin will report the container as unavailable.
68
+
69
+ ## Usage
70
+
71
+ The plugin exposes typed frontend helpers for invoking the native commands.
72
+
73
+ ```ts
74
+ import {
75
+ getContainerStatus,
76
+ forContainer,
77
+ readFile,
78
+ writeFile,
79
+ watchDirectory,
80
+ onDirectoryChanged,
81
+ } from "tauri-plugin-icloud-container-api";
82
+
83
+ const status = await getContainerStatus();
84
+ if (!status.available) {
85
+ throw new Error(status.reason ?? "iCloud container unavailable");
86
+ }
87
+
88
+ await writeFile("notes/example.txt", "hello iCloud");
89
+ const file = await readFile("notes/example.txt", { encoding: "utf8" });
90
+
91
+ const watchId = await watchDirectory("notes", true);
92
+ const unlisten = await onDirectoryChanged((event) => {
93
+ console.log(event.watchId, event.entries);
94
+ });
95
+
96
+ await unlisten();
97
+ ```
98
+
99
+ If your app works against a specific ubiquity container for a whole workflow, use a bound container helper instead of passing the identifier on every call.
100
+
101
+ ```ts
102
+ import { forContainer } from "tauri-plugin-icloud-container-api";
103
+
104
+ const container = forContainer("iCloud.com.example.app");
105
+
106
+ await container.writeFile("notes/example.txt", "hello iCloud");
107
+ const file = await container.readFile("notes/example.txt", {
108
+ encoding: "utf8",
109
+ });
110
+ const watchId = await container.watchDirectory("notes", true);
111
+ ```
112
+
113
+ Binary payloads use `Uint8Array` only. The guest API does not expose a base64 transport path.
114
+
115
+ ## Permissions
116
+
117
+ The plugin ships Tauri ACL entries for all 21 commands under [permissions/autogenerated/commands](permissions/autogenerated/commands).
118
+
119
+ The default permission set in [permissions/default.toml](permissions/default.toml) currently allows all commands. Host apps can replace that with a narrower allowlist in their own capability configuration.
120
+
121
+ ## API Surface
122
+
123
+ ### Container identity
124
+
125
+ | Command | Native API | What it does |
126
+ | -------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
127
+ | `get_container_status()` | `FileManager.ubiquityIdentityToken` + nil-check on `url(forUbiquityContainerIdentifier:)` | Returns `available`, `unavailable`, or `not_signed_in`. Must be called on a background thread; result is cached until `NSUbiquityIdentityDidChange` fires. |
128
+ | `get_container_url(identifier?)` | `FileManager.url(forUbiquityContainerIdentifier:)` | Resolves and returns the absolute path to the iCloud container root. Passing `nil` uses the app's default container. Required before constructing any path-based argument to another command. |
129
+
130
+ ### Coordinated file I/O
131
+
132
+ All reads and writes go through `NSFileCoordinator` to respect concurrent iCloud syncing.
133
+
134
+ | Command | Native API | What it does |
135
+ | ------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
136
+ | `read_file(path, options?)` | `NSFileCoordinator` read to `Data` | Reads file content using one command. `options.encoding` defaults to `utf8`; set `bytes` for binary payloads. Returns `{ encoding: "utf8", content: string }` or `{ encoding: "bytes", content: Uint8Array }`. |
137
+ | `write_file(path, content, options?)` | Coordinated write via `NSFileCoordinator` | Writes content. `options.encoding` defaults to `utf8`; set `bytes` for binary (`Uint8Array`). `options.fileProtection` sets iOS encryption level. Overwrites by default unless `options.overwrite = false`. The parent directory must already exist. |
138
+ | `create_file(path, options?)` | Coordinated write via `NSFileCoordinator` | Creates a new file with optional initial content and encoding (`utf8` or `bytes`). `options.fileProtection` sets iOS encryption level. Fails if the file already exists, or if the parent directory does not already exist. |
139
+ | `item_exists(path)` | `FileManager.fileExists(atPath:isDirectory:)` | Returns whether an item exists at the path and whether it is a file or directory. Does not trigger a download. |
140
+ | `get_attributes(path)` | `FileManager.attributesOfItem` + `URL.resourceValues` for ubiquitous keys | Returns file size, modification date, creation date, type, and iCloud-specific resource values such as sync status. |
141
+
142
+ ### Directory operations
143
+
144
+ | Command | Native API | What it does |
145
+ | ------------------------------------------ | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
146
+ | `create_directory(path, options?)` | `FileManager.createDirectory(at:withIntermediateDirectories:attributes:)` | Creates a directory with optional ancestor creation. `options.withIntermediateDirectories` defaults to `true`. `options.fileProtection` sets encryption. |
147
+ | `list_directory(path, options?)` | `FileManager.contentsOfDirectory` / `FileManager.enumerator` + `URL.resourceValues` | Lists entries in one command. `options.recursive = false` performs shallow listing; `true` returns all descendants. `options.skipsHiddenFiles = false` includes dot-files. Entries include name, path, file metadata, and sync status when iCloud metadata is available for an item. |
148
+ | `delete_item(path)` | `NSFileCoordinator` + `FileManager.removeItem` | Permanently deletes a file or directory and all its contents. Cannot be undone. |
149
+ | `trash_item(path)` | `NSFileCoordinator` + `FileManager.trashItem(at:resultingItemURL:)` | Moves the item to native Trash using the platform API. Returns `{ path: string }` with the resulting absolute trash path. |
150
+ | `move_item(source_path, destination_path)` | `NSFileCoordinator` writing both URLs + `FileManager.moveItem` | Moves a file or directory within the container. Can also serve as rename-in-place when source and destination share the same parent. The destination parent must already exist, and the destination path must not already exist. |
151
+ | `copy_item(source_path, destination_path)` | `NSFileCoordinator` + `FileManager.copyItem` | Copies a file or directory to a new path within the container. The destination parent must already exist, and the destination path must not already exist. |
152
+
153
+ ### iCloud sync controls
154
+
155
+ | Command | Native API | What it does |
156
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
157
+ | `get_item_sync_status(path)` | `URL.resourceValues` for `ubiquitousItemDownloadingStatus`, `ubiquitousItemIsDownloading`, `ubiquitousItemIsUploading`, `ubiquitousItemIsUploaded`, `ubiquitousItemDownloadingError`, `ubiquitousItemUploadingError` | Returns the current iCloud transfer state for a single item: download phase, upload/download activity flags, and any transfer errors exposed through Foundation resource values. |
158
+ | `start_download(path)` | `FileManager.startDownloadingUbiquitousItem(at:)` | Tells iCloud to fetch the local copy of a cloud-only file. Returns immediately; poll `get_item_sync_status` or use `watch_directory` to observe progress. |
159
+ | `evict_item(path)` | `FileManager.evictUbiquitousItem(at:)` | Removes the local copy of a file that is safely stored in iCloud, freeing device storage. The file remains visible in `list_directory` with status `notDownloaded`. |
160
+ | `is_ubiquitous(path)` | `FileManager.isUbiquitousItem(at:)` | Returns whether the item at the given path is managed by iCloud. Useful when handling paths that may come from outside the container. |
161
+
162
+ ### File watching
163
+
164
+ | Command | Native API | What it does |
165
+ | ---------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
166
+ | `watch_directory(path, recursive)` | `NSMetadataQuery` live subscription | Starts a live-updating observer on a directory. Emits the `icloud://directory-changed` event on every iCloud-sync or local change. Returns a `watchId` for later cancellation. |
167
+ | `unwatch(watch_id)` | `NSMetadataQuery.stop()` + dealloc | Cancels and releases the watcher registered under `watchId`. |
168
+ | `watch_file(path)` | `NSFilePresenter` | Registers a file-level presenter that emits the `icloud://file-changed` event when the file is modified or its sync state changes. Returns a `watchId`. |
169
+ | `unwatch_file(watch_id)` | `NSFileCoordinator.removeFilePresenter` | Removes the `NSFilePresenter` registered under `watchId`. |
170
+
171
+ Watch events emitted by the guest API:
172
+
173
+ - `icloud://directory-changed`
174
+ - `icloud://file-changed`
@@ -0,0 +1,366 @@
1
+ import { addPluginListener, invoke, type PluginListener } from '@tauri-apps/api/core'
2
+
3
+ const PLUGIN_NAME = 'icloud-container'
4
+ const COMMAND_PREFIX = `plugin:${PLUGIN_NAME}|`
5
+ const textEncoder = new TextEncoder()
6
+
7
+ export const DIRECTORY_CHANGED_EVENT = 'icloud://directory-changed'
8
+ export const FILE_CHANGED_EVENT = 'icloud://file-changed'
9
+
10
+ export type FileEncoding = 'utf8' | 'bytes'
11
+ export type FileProtectionType =
12
+ | 'complete'
13
+ | 'completeUnlessOpen'
14
+ | 'completeUntilFirstUserAuth'
15
+ | 'none'
16
+
17
+ export interface ContainerStatus {
18
+ available: boolean
19
+ reason?: string | null
20
+ }
21
+
22
+ export interface Utf8FileContent {
23
+ encoding: 'utf8'
24
+ content: string
25
+ }
26
+
27
+ export interface BytesFileContent {
28
+ encoding: 'bytes'
29
+ content: Uint8Array
30
+ }
31
+
32
+ export type FileContent = Utf8FileContent | BytesFileContent
33
+
34
+ export interface FolderEntry {
35
+ name: string
36
+ path: string
37
+ isDirectory: boolean
38
+ size?: number | null
39
+ modifiedDate?: number | null
40
+ createdDate?: number | null
41
+ syncStatus?: SyncStatus | null
42
+ }
43
+
44
+ export interface ItemExistence {
45
+ exists: boolean
46
+ isDirectory: boolean
47
+ }
48
+
49
+ export interface SyncStatus {
50
+ phase: 'current' | 'notDownloaded' | 'downloaded'
51
+ isDownloading: boolean
52
+ isUploading: boolean
53
+ isUploaded: boolean
54
+ downloadError?: string | null
55
+ uploadError?: string | null
56
+ }
57
+
58
+ export interface ItemAttributes {
59
+ size: number
60
+ modifiedDate: number
61
+ createdDate: number
62
+ type: 'file' | 'dir'
63
+ syncStatus?: SyncStatus | null
64
+ }
65
+
66
+ export interface TrashItemResult {
67
+ path: string
68
+ }
69
+
70
+ export interface WriteFileOptions {
71
+ encoding?: FileEncoding
72
+ overwrite?: boolean
73
+ fileProtection?: FileProtectionType
74
+ }
75
+
76
+ export interface ReadFileOptions {
77
+ encoding?: FileEncoding
78
+ }
79
+
80
+ export interface CreateFileOptions {
81
+ content?: string | Uint8Array
82
+ encoding?: FileEncoding
83
+ fileProtection?: FileProtectionType
84
+ }
85
+
86
+ export interface CreateDirectoryOptions {
87
+ withIntermediateDirectories?: boolean
88
+ fileProtection?: FileProtectionType
89
+ }
90
+
91
+ export interface ListDirectoryOptions {
92
+ recursive?: boolean
93
+ skipsHiddenFiles?: boolean
94
+ }
95
+
96
+ export interface DirectoryWatchEvent {
97
+ watchId: string
98
+ path: string
99
+ recursive: boolean
100
+ entries: string[]
101
+ }
102
+
103
+ export interface FileWatchEvent {
104
+ watchId: string
105
+ path: string
106
+ }
107
+
108
+ export type ContainerIdentifier = string | undefined
109
+
110
+ export interface ICloudContainerHandle {
111
+ readonly identifier: string
112
+ getStatus(): Promise<ContainerStatus>
113
+ getUrl(): Promise<string>
114
+ readFile(path: string, options?: ReadFileOptions): Promise<FileContent>
115
+ writeFile(path: string, content: string | Uint8Array, options?: WriteFileOptions): Promise<void>
116
+ createFile(path: string, options?: CreateFileOptions): Promise<FolderEntry>
117
+ itemExists(path: string): Promise<ItemExistence>
118
+ getAttributes(path: string): Promise<ItemAttributes>
119
+ createDirectory(path: string, options?: CreateDirectoryOptions): Promise<void>
120
+ listDirectory(path: string, options?: ListDirectoryOptions): Promise<FolderEntry[]>
121
+ deleteItem(path: string): Promise<void>
122
+ trashItem(path: string): Promise<TrashItemResult>
123
+ moveItem(sourcePath: string, destinationPath: string): Promise<FolderEntry>
124
+ copyItem(sourcePath: string, destinationPath: string): Promise<FolderEntry>
125
+ getItemSyncStatus(path: string): Promise<SyncStatus>
126
+ startDownload(path: string): Promise<void>
127
+ evictItem(path: string): Promise<void>
128
+ isUbiquitous(path: string): Promise<boolean>
129
+ watchDirectory(path: string, recursive?: boolean): Promise<string>
130
+ watchFile(path: string): Promise<string>
131
+ }
132
+
133
+ function invokeCommand<T>(command: string, args?: Record<string, unknown>): Promise<T> {
134
+ return invoke(`${COMMAND_PREFIX}${command}`, args)
135
+ }
136
+
137
+ function inferEncoding(content: string | Uint8Array | undefined, encoding?: FileEncoding): FileEncoding {
138
+ if (encoding) {
139
+ return encoding
140
+ }
141
+
142
+ return content instanceof Uint8Array ? 'bytes' : 'utf8'
143
+ }
144
+
145
+ function toCreateFilePayload(options?: CreateFileOptions): {
146
+ content?: Uint8Array
147
+ encoding?: FileEncoding
148
+ fileProtection?: FileProtectionType
149
+ } | undefined {
150
+ if (!options) {
151
+ return undefined
152
+ }
153
+
154
+ const encoding = inferEncoding(options.content, options.encoding)
155
+ const content =
156
+ typeof options.content === 'string'
157
+ ? textEncoder.encode(options.content)
158
+ : options.content
159
+
160
+ return {
161
+ content,
162
+ encoding,
163
+ fileProtection: options.fileProtection,
164
+ }
165
+ }
166
+
167
+ function toWriteFilePayload(content: string | Uint8Array, encoding?: FileEncoding): FileContent {
168
+ const resolvedEncoding = inferEncoding(content, encoding)
169
+
170
+ if (content instanceof Uint8Array) {
171
+ if (resolvedEncoding !== 'bytes') {
172
+ throw new Error('Uint8Array content requires encoding "bytes"')
173
+ }
174
+
175
+ return { encoding: 'bytes', content }
176
+ }
177
+
178
+ if (resolvedEncoding === 'bytes') {
179
+ return { encoding: 'bytes', content: textEncoder.encode(content) }
180
+ }
181
+
182
+ return { encoding: 'utf8', content }
183
+ }
184
+
185
+ export async function getContainerStatus(identifier?: string): Promise<ContainerStatus> {
186
+ return invokeCommand('get_container_status', { identifier })
187
+ }
188
+
189
+ export async function getContainerUrl(identifier?: string): Promise<string> {
190
+ return invokeCommand('get_container_url', { identifier })
191
+ }
192
+
193
+ export async function readFile(
194
+ path: string,
195
+ options?: ReadFileOptions,
196
+ identifier?: ContainerIdentifier,
197
+ ): Promise<FileContent> {
198
+ return invokeCommand('read_file', { path, identifier, options })
199
+ }
200
+
201
+ export async function writeFile(
202
+ path: string,
203
+ content: string | Uint8Array,
204
+ options?: WriteFileOptions,
205
+ identifier?: ContainerIdentifier,
206
+ ): Promise<void> {
207
+ return invokeCommand('write_file', {
208
+ path,
209
+ identifier,
210
+ content: toWriteFilePayload(content, options?.encoding),
211
+ options,
212
+ })
213
+ }
214
+
215
+ export async function createFile(
216
+ path: string,
217
+ options?: CreateFileOptions,
218
+ identifier?: ContainerIdentifier,
219
+ ): Promise<FolderEntry> {
220
+ return invokeCommand('create_file', {
221
+ path,
222
+ identifier,
223
+ options: toCreateFilePayload(options),
224
+ })
225
+ }
226
+
227
+ export async function itemExists(
228
+ path: string,
229
+ identifier?: ContainerIdentifier,
230
+ ): Promise<ItemExistence> {
231
+ return invokeCommand('item_exists', { path, identifier })
232
+ }
233
+
234
+ export async function getAttributes(
235
+ path: string,
236
+ identifier?: ContainerIdentifier,
237
+ ): Promise<ItemAttributes> {
238
+ return invokeCommand('get_attributes', { path, identifier })
239
+ }
240
+
241
+ export async function createDirectory(
242
+ path: string,
243
+ options?: CreateDirectoryOptions,
244
+ identifier?: ContainerIdentifier,
245
+ ): Promise<void> {
246
+ return invokeCommand('create_directory', { path, identifier, options })
247
+ }
248
+
249
+ export async function listDirectory(
250
+ path: string,
251
+ options?: ListDirectoryOptions,
252
+ identifier?: ContainerIdentifier,
253
+ ): Promise<FolderEntry[]> {
254
+ return invokeCommand('list_directory', { path, identifier, options })
255
+ }
256
+
257
+ export async function deleteItem(path: string, identifier?: ContainerIdentifier): Promise<void> {
258
+ return invokeCommand('delete_item', { path, identifier })
259
+ }
260
+
261
+ export async function trashItem(
262
+ path: string,
263
+ identifier?: ContainerIdentifier,
264
+ ): Promise<TrashItemResult> {
265
+ return invokeCommand('trash_item', { path, identifier })
266
+ }
267
+
268
+ export async function moveItem(
269
+ sourcePath: string,
270
+ destinationPath: string,
271
+ identifier?: ContainerIdentifier,
272
+ ): Promise<FolderEntry> {
273
+ return invokeCommand('move_item', { sourcePath, destinationPath, identifier })
274
+ }
275
+
276
+ export async function copyItem(
277
+ sourcePath: string,
278
+ destinationPath: string,
279
+ identifier?: ContainerIdentifier,
280
+ ): Promise<FolderEntry> {
281
+ return invokeCommand('copy_item', { sourcePath, destinationPath, identifier })
282
+ }
283
+
284
+ export async function getItemSyncStatus(
285
+ path: string,
286
+ identifier?: ContainerIdentifier,
287
+ ): Promise<SyncStatus> {
288
+ return invokeCommand('get_item_sync_status', { path, identifier })
289
+ }
290
+
291
+ export async function startDownload(path: string, identifier?: ContainerIdentifier): Promise<void> {
292
+ return invokeCommand('start_download', { path, identifier })
293
+ }
294
+
295
+ export async function evictItem(path: string, identifier?: ContainerIdentifier): Promise<void> {
296
+ return invokeCommand('evict_item', { path, identifier })
297
+ }
298
+
299
+ export async function isUbiquitous(
300
+ path: string,
301
+ identifier?: ContainerIdentifier,
302
+ ): Promise<boolean> {
303
+ return invokeCommand('is_ubiquitous', { path, identifier })
304
+ }
305
+
306
+ export async function watchDirectory(
307
+ path: string,
308
+ recursive = false,
309
+ identifier?: ContainerIdentifier,
310
+ ): Promise<string> {
311
+ return invokeCommand('watch_directory', { path, recursive, identifier })
312
+ }
313
+
314
+ export async function unwatch(watchId: string): Promise<void> {
315
+ return invokeCommand('unwatch', { watchId })
316
+ }
317
+
318
+ export async function watchFile(path: string, identifier?: ContainerIdentifier): Promise<string> {
319
+ return invokeCommand('watch_file', { path, identifier })
320
+ }
321
+
322
+ export async function unwatchFile(watchId: string): Promise<void> {
323
+ return invokeCommand('unwatch_file', { watchId })
324
+ }
325
+
326
+ export async function onDirectoryChanged(
327
+ handler: (event: DirectoryWatchEvent) => void,
328
+ ): Promise<PluginListener> {
329
+ return addPluginListener<DirectoryWatchEvent>(PLUGIN_NAME, DIRECTORY_CHANGED_EVENT, handler)
330
+ }
331
+
332
+ export async function onFileChanged(
333
+ handler: (event: FileWatchEvent) => void,
334
+ ): Promise<PluginListener> {
335
+ return addPluginListener<FileWatchEvent>(PLUGIN_NAME, FILE_CHANGED_EVENT, handler)
336
+ }
337
+
338
+ export function forContainer(identifier: string): ICloudContainerHandle {
339
+ const trimmedIdentifier = identifier.trim()
340
+ if (!trimmedIdentifier) {
341
+ throw new Error('identifier is required and cannot be empty')
342
+ }
343
+
344
+ return {
345
+ identifier: trimmedIdentifier,
346
+ getStatus: () => getContainerStatus(trimmedIdentifier),
347
+ getUrl: () => getContainerUrl(trimmedIdentifier),
348
+ readFile: (path, options) => readFile(path, options, trimmedIdentifier),
349
+ writeFile: (path, content, options) => writeFile(path, content, options, trimmedIdentifier),
350
+ createFile: (path, options) => createFile(path, options, trimmedIdentifier),
351
+ itemExists: (path) => itemExists(path, trimmedIdentifier),
352
+ getAttributes: (path) => getAttributes(path, trimmedIdentifier),
353
+ createDirectory: (path, options) => createDirectory(path, options, trimmedIdentifier),
354
+ listDirectory: (path, options) => listDirectory(path, options, trimmedIdentifier),
355
+ deleteItem: (path) => deleteItem(path, trimmedIdentifier),
356
+ trashItem: (path) => trashItem(path, trimmedIdentifier),
357
+ moveItem: (sourcePath, destinationPath) => moveItem(sourcePath, destinationPath, trimmedIdentifier),
358
+ copyItem: (sourcePath, destinationPath) => copyItem(sourcePath, destinationPath, trimmedIdentifier),
359
+ getItemSyncStatus: (path) => getItemSyncStatus(path, trimmedIdentifier),
360
+ startDownload: (path) => startDownload(path, trimmedIdentifier),
361
+ evictItem: (path) => evictItem(path, trimmedIdentifier),
362
+ isUbiquitous: (path) => isUbiquitous(path, trimmedIdentifier),
363
+ watchDirectory: (path, recursive) => watchDirectory(path, recursive, trimmedIdentifier),
364
+ watchFile: (path) => watchFile(path, trimmedIdentifier),
365
+ }
366
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "tauri-plugin-icloud-container-api",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "Typed guest JS API for tauri-plugin-icloud-container",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/TensorBinge/tauri-plugin-icloud-container",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TensorBinge/tauri-plugin-icloud-container.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/TensorBinge/tauri-plugin-icloud-container/issues"
13
+ },
14
+ "type": "module",
15
+ "files": [
16
+ "guest-js",
17
+ "README.md"
18
+ ],
19
+ "main": "./guest-js/index.ts",
20
+ "types": "./guest-js/index.ts",
21
+ "exports": {
22
+ ".": "./guest-js/index.ts"
23
+ },
24
+ "scripts": {
25
+ "test": "node ./tests/guest_api_contract.mjs",
26
+ "test:guest-api": "node ./tests/guest_api_contract.mjs",
27
+ "pack:artifact": "mkdir -p artifacts && npm pack --pack-destination artifacts",
28
+ "prepublishOnly": "npm test"
29
+ },
30
+ "peerDependencies": {
31
+ "@tauri-apps/api": "^2.0.0"
32
+ }
33
+ }