@choochmeque/tauri-apple-extensions 0.1.2 → 0.2.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 +1 -1
- package/README.md +21 -7
- package/dist/cli.js +92 -51
- package/dist/core/entitlements.d.ts +6 -3
- package/dist/core/project-discovery.d.ts +2 -2
- package/dist/index.js +75 -43
- package/dist/types.d.ts +4 -2
- package/package.json +5 -2
- package/templates/macos/share/Info.plist +47 -0
- package/templates/macos/share/ShareViewController.swift +122 -0
- /package/templates/{share → ios/share}/Info.plist +0 -0
- /package/templates/{share → ios/share}/ShareViewController.swift +0 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# tauri-apple-extensions
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@choochmeque/tauri-apple-extensions)
|
|
4
|
+
[](https://codecov.io/gh/Choochmeque/tauri-apple-extensions)
|
|
4
5
|
[](LICENSE)
|
|
5
6
|
|
|
6
|
-
Add iOS extensions to Tauri apps with a single command.
|
|
7
|
+
Add iOS and macOS extensions to Tauri apps with a single command.
|
|
7
8
|
|
|
8
9
|
## Features
|
|
9
10
|
|
|
10
11
|
- Automatic Xcode project configuration via XcodeGen
|
|
11
12
|
- Built-in skeleton templates with TODO markers
|
|
12
13
|
- Plugin-based template system for custom implementations
|
|
14
|
+
- Supports both iOS and macOS platforms
|
|
13
15
|
- Idempotent - safe to re-run
|
|
14
16
|
|
|
15
17
|
## Prerequisites
|
|
16
18
|
|
|
17
19
|
- [XcodeGen](https://github.com/yonaskolb/XcodeGen) installed (`brew install xcodegen`)
|
|
18
|
-
- Tauri iOS project initialized (`tauri ios init`)
|
|
20
|
+
- Tauri iOS project initialized (`tauri ios init`) for iOS extensions
|
|
21
|
+
- For macOS extensions: Tauri macOS Xcode project via [@choochmeque/tauri-macos-xcode](https://github.com/Choochmeque/tauri-macos-xcode)
|
|
19
22
|
|
|
20
23
|
## Installation
|
|
21
24
|
|
|
@@ -36,7 +39,11 @@ bun add -D @choochmeque/tauri-apple-extensions
|
|
|
36
39
|
## Usage
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
|
-
|
|
42
|
+
# Add iOS Share Extension
|
|
43
|
+
npx @choochmeque/tauri-apple-extensions ios add share
|
|
44
|
+
|
|
45
|
+
# Add macOS Share Extension
|
|
46
|
+
npx @choochmeque/tauri-apple-extensions macos add share
|
|
40
47
|
```
|
|
41
48
|
|
|
42
49
|
This creates a Share Extension with a minimal skeleton template. Open the generated Swift file and implement your logic where you see `// TODO:` comments.
|
|
@@ -45,10 +52,10 @@ This creates a Share Extension with a minimal skeleton template. Open the genera
|
|
|
45
52
|
|
|
46
53
|
```bash
|
|
47
54
|
# Use templates from a plugin (plugin must include tauri-apple-extension config)
|
|
48
|
-
npx @choochmeque/tauri-apple-extensions add share --plugin <plugin-name>
|
|
55
|
+
npx @choochmeque/tauri-apple-extensions ios add share --plugin <plugin-name>
|
|
49
56
|
|
|
50
57
|
# Use custom templates directory
|
|
51
|
-
npx @choochmeque/tauri-apple-extensions add share --templates ./path/to/templates
|
|
58
|
+
npx @choochmeque/tauri-apple-extensions ios add share --templates ./path/to/templates
|
|
52
59
|
```
|
|
53
60
|
|
|
54
61
|
> **Note:** When using `--plugin`, the plugin's `package.json` must contain a `tauri-apple-extension` config. See [For Plugin Developers](#for-plugin-developers) below.
|
|
@@ -63,7 +70,9 @@ npx @choochmeque/tauri-apple-extensions add share --templates ./path/to/template
|
|
|
63
70
|
|
|
64
71
|
After running the tool:
|
|
65
72
|
|
|
66
|
-
1. Open the Xcode project
|
|
73
|
+
1. Open the Xcode project:
|
|
74
|
+
- iOS: `src-tauri/gen/apple/*.xcodeproj`
|
|
75
|
+
- macOS: `src-tauri/gen/apple-macos/*.xcodeproj`
|
|
67
76
|
2. Select your Apple Developer Team for both targets
|
|
68
77
|
3. Enable **App Groups** capability for both targets
|
|
69
78
|
4. Configure App Groups in [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/list/applicationGroup)
|
|
@@ -76,11 +85,16 @@ To make your plugin compatible, add to your `package.json`:
|
|
|
76
85
|
{
|
|
77
86
|
"tauri-apple-extension": {
|
|
78
87
|
"type": "share",
|
|
79
|
-
"templates":
|
|
88
|
+
"templates": {
|
|
89
|
+
"ios": "./templates/ios",
|
|
90
|
+
"macos": "./templates/macos"
|
|
91
|
+
}
|
|
80
92
|
}
|
|
81
93
|
}
|
|
82
94
|
```
|
|
83
95
|
|
|
96
|
+
Plugins can support one or both platforms. If a user runs the command for a platform your plugin doesn't support, they'll receive a clear error message.
|
|
97
|
+
|
|
84
98
|
### Template Variables
|
|
85
99
|
|
|
86
100
|
| Variable | Description |
|
package/dist/cli.js
CHANGED
|
@@ -44,17 +44,22 @@ function findTauriConfig(projectRoot) {
|
|
|
44
44
|
}
|
|
45
45
|
throw new Error("Could not find tauri.conf.json");
|
|
46
46
|
}
|
|
47
|
-
function findAppleProjectDir(projectRoot) {
|
|
47
|
+
function findAppleProjectDir(projectRoot, platform) {
|
|
48
|
+
// iOS uses 'apple', macOS uses 'apple-macos'
|
|
49
|
+
const dirName = platform === "ios" ? "apple" : "apple-macos";
|
|
50
|
+
const initHint = platform === "ios"
|
|
51
|
+
? "Run 'tauri ios init' first."
|
|
52
|
+
: "Set up macOS Xcode project using @choochmeque/tauri-macos-xcode first.";
|
|
48
53
|
const paths = [
|
|
49
|
-
path.join(projectRoot, "src-tauri", "gen",
|
|
50
|
-
path.join(projectRoot, "gen",
|
|
54
|
+
path.join(projectRoot, "src-tauri", "gen", dirName),
|
|
55
|
+
path.join(projectRoot, "gen", dirName),
|
|
51
56
|
];
|
|
52
57
|
for (const p of paths) {
|
|
53
58
|
if (fs.existsSync(p)) {
|
|
54
59
|
return p;
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
|
-
throw new Error(
|
|
62
|
+
throw new Error(`Could not find ${platform} project directory. ${initHint}`);
|
|
58
63
|
}
|
|
59
64
|
function getAppInfo(tauriConfig, projectYml) {
|
|
60
65
|
const parsed = parseYamlSimple(projectYml);
|
|
@@ -157,52 +162,69 @@ function addDependencyToTarget(projectYml, mainTargetName, dependencyConfig) {
|
|
|
157
162
|
return modified;
|
|
158
163
|
}
|
|
159
164
|
|
|
160
|
-
|
|
161
|
-
const targetName = `${appInfo.productName}_iOS`;
|
|
162
|
-
const entitlementsPath = path.join(appleDir, targetName, `${targetName}.entitlements`);
|
|
163
|
-
const appGroupId = `group.${appInfo.identifier}`;
|
|
164
|
-
let entitlements;
|
|
165
|
-
if (fs.existsSync(entitlementsPath)) {
|
|
166
|
-
entitlements = fs.readFileSync(entitlementsPath, "utf8");
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
entitlements = `<?xml version="1.0" encoding="UTF-8"?>
|
|
165
|
+
const EMPTY_ENTITLEMENTS = `<?xml version="1.0" encoding="UTF-8"?>
|
|
170
166
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
171
167
|
<plist version="1.0">
|
|
172
168
|
<dict>
|
|
173
169
|
</dict>
|
|
174
170
|
</plist>`;
|
|
175
|
-
|
|
171
|
+
function addAppGroupToEntitlements(entitlements, appGroupId) {
|
|
176
172
|
// Check if app groups already configured
|
|
177
173
|
if (entitlements.includes("com.apple.security.application-groups")) {
|
|
178
174
|
if (!entitlements.includes(appGroupId)) {
|
|
179
175
|
// Add our group to existing array
|
|
180
|
-
|
|
176
|
+
return entitlements.replace(/(<key>com\.apple\.security\.application-groups<\/key>\s*<array>)/, `$1\n <string>${appGroupId}</string>`);
|
|
181
177
|
}
|
|
178
|
+
return entitlements;
|
|
182
179
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
entitlements = entitlements.replace(/<dict>\s*<\/dict>/, `<dict>
|
|
180
|
+
// Add app groups entitlement
|
|
181
|
+
return entitlements.replace(/<dict>\s*<\/dict>/, `<dict>
|
|
186
182
|
<key>com.apple.security.application-groups</key>
|
|
187
183
|
<array>
|
|
188
184
|
<string>${appGroupId}</string>
|
|
189
185
|
</array>
|
|
190
186
|
</dict>`);
|
|
191
|
-
}
|
|
192
|
-
fs.writeFileSync(entitlementsPath, entitlements);
|
|
193
|
-
console.log(`Updated main app entitlements: ${entitlementsPath}`);
|
|
194
187
|
}
|
|
195
|
-
function
|
|
196
|
-
const
|
|
188
|
+
function createEntitlementsContent(platform, appGroupId) {
|
|
189
|
+
const entries = [];
|
|
190
|
+
// macOS extensions require app-sandbox to be registered
|
|
191
|
+
if (platform === "macos") {
|
|
192
|
+
entries.push(` <key>com.apple.security.app-sandbox</key>
|
|
193
|
+
<true/>`);
|
|
194
|
+
}
|
|
195
|
+
// App groups only if needed
|
|
196
|
+
if (appGroupId) {
|
|
197
|
+
entries.push(` <key>com.apple.security.application-groups</key>
|
|
198
|
+
<array>
|
|
199
|
+
<string>${appGroupId}</string>
|
|
200
|
+
</array>`);
|
|
201
|
+
}
|
|
202
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
197
203
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
198
204
|
<plist version="1.0">
|
|
199
205
|
<dict>
|
|
200
|
-
|
|
201
|
-
<array>
|
|
202
|
-
<string>${appGroupId}</string>
|
|
203
|
-
</array>
|
|
206
|
+
${entries.join("\n")}
|
|
204
207
|
</dict>
|
|
205
208
|
</plist>`;
|
|
209
|
+
}
|
|
210
|
+
function updateMainAppEntitlements(appleDir, appInfo, platform) {
|
|
211
|
+
const platformSuffix = platform === "ios" ? "iOS" : "macOS";
|
|
212
|
+
const targetName = `${appInfo.productName}_${platformSuffix}`;
|
|
213
|
+
const entitlementsPath = path.join(appleDir, targetName, `${targetName}.entitlements`);
|
|
214
|
+
const appGroupId = `group.${appInfo.identifier}`;
|
|
215
|
+
let entitlements;
|
|
216
|
+
if (fs.existsSync(entitlementsPath)) {
|
|
217
|
+
entitlements = fs.readFileSync(entitlementsPath, "utf8");
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
entitlements = EMPTY_ENTITLEMENTS;
|
|
221
|
+
}
|
|
222
|
+
entitlements = addAppGroupToEntitlements(entitlements, appGroupId);
|
|
223
|
+
fs.writeFileSync(entitlementsPath, entitlements);
|
|
224
|
+
console.log(`Updated main app entitlements: ${entitlementsPath}`);
|
|
225
|
+
}
|
|
226
|
+
function createExtensionEntitlements(extensionDir, appGroupId, platform) {
|
|
227
|
+
const entitlements = createEntitlementsContent(platform, appGroupId);
|
|
206
228
|
const entitlementsPath = path.join(extensionDir, `${path.basename(extensionDir)}.entitlements`);
|
|
207
229
|
fs.writeFileSync(entitlementsPath, entitlements);
|
|
208
230
|
return entitlementsPath;
|
|
@@ -253,7 +275,7 @@ const shareExtension = {
|
|
|
253
275
|
extensionName(appInfo) {
|
|
254
276
|
return `${appInfo.productName}-ShareExtension`;
|
|
255
277
|
},
|
|
256
|
-
createFiles(appleDir, appInfo, templatesDir) {
|
|
278
|
+
createFiles(appleDir, appInfo, templatesDir, platform) {
|
|
257
279
|
const extensionDir = path.join(appleDir, "ShareExtension");
|
|
258
280
|
const appGroupId = `group.${appInfo.identifier}`;
|
|
259
281
|
const urlScheme = appInfo.productName
|
|
@@ -343,19 +365,22 @@ const shareExtension = {
|
|
|
343
365
|
fs.writeFileSync(path.join(extensionDir, "Info.plist"), defaultInfoPlist);
|
|
344
366
|
}
|
|
345
367
|
// Create entitlements
|
|
346
|
-
createExtensionEntitlements(extensionDir, appGroupId);
|
|
368
|
+
createExtensionEntitlements(extensionDir, appGroupId, platform);
|
|
347
369
|
console.log(`Created ShareExtension files in ${extensionDir}`);
|
|
348
370
|
},
|
|
349
|
-
updateProjectYml(projectYml, appInfo) {
|
|
371
|
+
updateProjectYml(projectYml, appInfo, platform) {
|
|
350
372
|
const extensionName = this.extensionName(appInfo);
|
|
351
373
|
const extensionBundleId = `${appInfo.identifier}.ShareExtension`;
|
|
352
|
-
const
|
|
374
|
+
const platformSuffix = platform === "ios" ? "iOS" : "macOS";
|
|
375
|
+
const platformValue = platform === "ios" ? "iOS" : "macOS";
|
|
376
|
+
const deploymentTarget = platform === "ios" ? "14.0" : "11.0";
|
|
377
|
+
const targetName = `${appInfo.productName}_${platformSuffix}`;
|
|
353
378
|
// Create the extension target YAML
|
|
354
379
|
const extensionTarget = `
|
|
355
380
|
${extensionName}:
|
|
356
381
|
type: app-extension
|
|
357
|
-
platform:
|
|
358
|
-
deploymentTarget: "
|
|
382
|
+
platform: ${platformValue}
|
|
383
|
+
deploymentTarget: "${deploymentTarget}"
|
|
359
384
|
sources:
|
|
360
385
|
- path: ShareExtension
|
|
361
386
|
info:
|
|
@@ -404,6 +429,7 @@ const EXTENSIONS = {
|
|
|
404
429
|
share: shareExtension,
|
|
405
430
|
};
|
|
406
431
|
function resolveTemplatesDir(options, extensionType) {
|
|
432
|
+
const { platform } = options;
|
|
407
433
|
// Option 1: Explicit templates path
|
|
408
434
|
if (options.templates) {
|
|
409
435
|
const templatesPath = path.resolve(process.cwd(), options.templates);
|
|
@@ -423,16 +449,20 @@ function resolveTemplatesDir(options, extensionType) {
|
|
|
423
449
|
if (extensionConfig.type !== extensionType) {
|
|
424
450
|
throw new Error(`Plugin ${options.plugin} is for '${extensionConfig.type}' extension, not '${extensionType}'`);
|
|
425
451
|
}
|
|
426
|
-
const
|
|
452
|
+
const platformTemplates = extensionConfig.templates[platform];
|
|
453
|
+
if (!platformTemplates) {
|
|
454
|
+
throw new Error(`Plugin ${options.plugin} does not support ${platform} platform`);
|
|
455
|
+
}
|
|
456
|
+
const templatesPath = path.join(pluginPath, platformTemplates);
|
|
427
457
|
if (!fs.existsSync(templatesPath)) {
|
|
428
458
|
throw new Error(`Plugin templates directory not found: ${templatesPath}`);
|
|
429
459
|
}
|
|
430
460
|
return templatesPath;
|
|
431
461
|
}
|
|
432
|
-
// Option 3: Default templates (from bundled dist/cli.js -> ../templates)
|
|
433
|
-
const defaultTemplates = path.join(__dirname$1, "../templates", extensionType);
|
|
462
|
+
// Option 3: Default templates (from bundled dist/cli.js -> ../templates/{platform}/{type})
|
|
463
|
+
const defaultTemplates = path.join(__dirname$1, "../templates", platform, extensionType);
|
|
434
464
|
if (!fs.existsSync(defaultTemplates)) {
|
|
435
|
-
throw new Error(`No templates found. Use --plugin or --templates to specify templates.`);
|
|
465
|
+
throw new Error(`No templates found for ${platform}/${extensionType}. Use --plugin or --templates to specify templates.`);
|
|
436
466
|
}
|
|
437
467
|
return defaultTemplates;
|
|
438
468
|
}
|
|
@@ -450,7 +480,9 @@ function resolvePluginPath(pluginName) {
|
|
|
450
480
|
throw new Error(`Plugin ${pluginName} not found in node_modules. Make sure it's installed.`);
|
|
451
481
|
}
|
|
452
482
|
async function addExtension(type, options) {
|
|
453
|
-
|
|
483
|
+
const { platform } = options;
|
|
484
|
+
const platformDisplay = platform === "ios" ? "iOS" : "macOS";
|
|
485
|
+
console.log(`\nTauri Apple Extensions - Add ${type} (${platformDisplay})\n`);
|
|
454
486
|
try {
|
|
455
487
|
// Validate extension type
|
|
456
488
|
const extension = EXTENSIONS[type];
|
|
@@ -462,7 +494,7 @@ async function addExtension(type, options) {
|
|
|
462
494
|
const projectRoot = findProjectRoot();
|
|
463
495
|
console.log(`Project root: ${projectRoot}`);
|
|
464
496
|
const tauriConfig = findTauriConfig(projectRoot);
|
|
465
|
-
const appleDir = findAppleProjectDir(projectRoot);
|
|
497
|
+
const appleDir = findAppleProjectDir(projectRoot, platform);
|
|
466
498
|
console.log(`Apple project dir: ${appleDir}`);
|
|
467
499
|
// Get app info
|
|
468
500
|
let projectYml = readProjectYml(appleDir);
|
|
@@ -482,12 +514,12 @@ async function addExtension(type, options) {
|
|
|
482
514
|
console.log(`\nUsing templates from: ${templatesDir}`);
|
|
483
515
|
// Run extension setup
|
|
484
516
|
console.log(`\n1. Creating ${extension.displayName} files...`);
|
|
485
|
-
extension.createFiles(appleDir, appInfo, templatesDir);
|
|
517
|
+
extension.createFiles(appleDir, appInfo, templatesDir, platform);
|
|
486
518
|
console.log(`\n2. Updating main app entitlements...`);
|
|
487
|
-
updateMainAppEntitlements(appleDir, appInfo);
|
|
519
|
+
updateMainAppEntitlements(appleDir, appInfo, platform);
|
|
488
520
|
console.log(`\n3. Updating project.yml (extension target + URL scheme)...`);
|
|
489
521
|
projectYml = readProjectYml(appleDir);
|
|
490
|
-
projectYml = extension.updateProjectYml(projectYml, appInfo);
|
|
522
|
+
projectYml = extension.updateProjectYml(projectYml, appInfo, platform);
|
|
491
523
|
writeProjectYml(appleDir, projectYml);
|
|
492
524
|
console.log(`\n4. Regenerating Xcode project...`);
|
|
493
525
|
runXcodeGen(appleDir);
|
|
@@ -512,12 +544,21 @@ async function addExtension(type, options) {
|
|
|
512
544
|
const program = new Command();
|
|
513
545
|
program
|
|
514
546
|
.name("tauri-apple-extensions")
|
|
515
|
-
.description("Add
|
|
516
|
-
.version("0.1
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
.description("
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
547
|
+
.description("Add Apple extensions to Tauri apps")
|
|
548
|
+
.version("0.2.1");
|
|
549
|
+
function createPlatformCommand(platform) {
|
|
550
|
+
const cmd = new Command(platform);
|
|
551
|
+
cmd.description(`Manage ${platform === "ios" ? "iOS" : "macOS"} extensions`);
|
|
552
|
+
cmd
|
|
553
|
+
.command("add <type>")
|
|
554
|
+
.description("Add an extension (e.g., share)")
|
|
555
|
+
.option("-p, --plugin <name>", "Plugin to use for templates")
|
|
556
|
+
.option("-t, --templates <path>", "Custom templates directory")
|
|
557
|
+
.action((type, options) => {
|
|
558
|
+
addExtension(type, { ...options, platform });
|
|
559
|
+
});
|
|
560
|
+
return cmd;
|
|
561
|
+
}
|
|
562
|
+
program.addCommand(createPlatformCommand("ios"));
|
|
563
|
+
program.addCommand(createPlatformCommand("macos"));
|
|
523
564
|
program.parse();
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
import type { AppInfo } from "../types.js";
|
|
2
|
-
export declare
|
|
3
|
-
export declare function
|
|
1
|
+
import type { AppInfo, Platform } from "../types.js";
|
|
2
|
+
export declare const EMPTY_ENTITLEMENTS = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n</dict>\n</plist>";
|
|
3
|
+
export declare function addAppGroupToEntitlements(entitlements: string, appGroupId: string): string;
|
|
4
|
+
export declare function createEntitlementsContent(platform: Platform, appGroupId?: string): string;
|
|
5
|
+
export declare function updateMainAppEntitlements(appleDir: string, appInfo: AppInfo, platform: Platform): void;
|
|
6
|
+
export declare function createExtensionEntitlements(extensionDir: string, appGroupId: string, platform: Platform): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AppInfo, TauriConfig } from "../types.js";
|
|
1
|
+
import type { AppInfo, TauriConfig, Platform } from "../types.js";
|
|
2
2
|
export declare function findProjectRoot(): string;
|
|
3
3
|
export declare function findTauriConfig(projectRoot: string): TauriConfig;
|
|
4
|
-
export declare function findAppleProjectDir(projectRoot: string): string;
|
|
4
|
+
export declare function findAppleProjectDir(projectRoot: string, platform: Platform): string;
|
|
5
5
|
export declare function getAppInfo(tauriConfig: TauriConfig, projectYml: string): AppInfo;
|
package/dist/index.js
CHANGED
|
@@ -42,17 +42,22 @@ function findTauriConfig(projectRoot) {
|
|
|
42
42
|
}
|
|
43
43
|
throw new Error("Could not find tauri.conf.json");
|
|
44
44
|
}
|
|
45
|
-
function findAppleProjectDir(projectRoot) {
|
|
45
|
+
function findAppleProjectDir(projectRoot, platform) {
|
|
46
|
+
// iOS uses 'apple', macOS uses 'apple-macos'
|
|
47
|
+
const dirName = platform === "ios" ? "apple" : "apple-macos";
|
|
48
|
+
const initHint = platform === "ios"
|
|
49
|
+
? "Run 'tauri ios init' first."
|
|
50
|
+
: "Set up macOS Xcode project using @choochmeque/tauri-macos-xcode first.";
|
|
46
51
|
const paths = [
|
|
47
|
-
path.join(projectRoot, "src-tauri", "gen",
|
|
48
|
-
path.join(projectRoot, "gen",
|
|
52
|
+
path.join(projectRoot, "src-tauri", "gen", dirName),
|
|
53
|
+
path.join(projectRoot, "gen", dirName),
|
|
49
54
|
];
|
|
50
55
|
for (const p of paths) {
|
|
51
56
|
if (fs.existsSync(p)) {
|
|
52
57
|
return p;
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
|
-
throw new Error(
|
|
60
|
+
throw new Error(`Could not find ${platform} project directory. ${initHint}`);
|
|
56
61
|
}
|
|
57
62
|
function getAppInfo(tauriConfig, projectYml) {
|
|
58
63
|
const parsed = parseYamlSimple(projectYml);
|
|
@@ -155,52 +160,69 @@ function addDependencyToTarget(projectYml, mainTargetName, dependencyConfig) {
|
|
|
155
160
|
return modified;
|
|
156
161
|
}
|
|
157
162
|
|
|
158
|
-
|
|
159
|
-
const targetName = `${appInfo.productName}_iOS`;
|
|
160
|
-
const entitlementsPath = path.join(appleDir, targetName, `${targetName}.entitlements`);
|
|
161
|
-
const appGroupId = `group.${appInfo.identifier}`;
|
|
162
|
-
let entitlements;
|
|
163
|
-
if (fs.existsSync(entitlementsPath)) {
|
|
164
|
-
entitlements = fs.readFileSync(entitlementsPath, "utf8");
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
entitlements = `<?xml version="1.0" encoding="UTF-8"?>
|
|
163
|
+
const EMPTY_ENTITLEMENTS = `<?xml version="1.0" encoding="UTF-8"?>
|
|
168
164
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
169
165
|
<plist version="1.0">
|
|
170
166
|
<dict>
|
|
171
167
|
</dict>
|
|
172
168
|
</plist>`;
|
|
173
|
-
|
|
169
|
+
function addAppGroupToEntitlements(entitlements, appGroupId) {
|
|
174
170
|
// Check if app groups already configured
|
|
175
171
|
if (entitlements.includes("com.apple.security.application-groups")) {
|
|
176
172
|
if (!entitlements.includes(appGroupId)) {
|
|
177
173
|
// Add our group to existing array
|
|
178
|
-
|
|
174
|
+
return entitlements.replace(/(<key>com\.apple\.security\.application-groups<\/key>\s*<array>)/, `$1\n <string>${appGroupId}</string>`);
|
|
179
175
|
}
|
|
176
|
+
return entitlements;
|
|
180
177
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
entitlements = entitlements.replace(/<dict>\s*<\/dict>/, `<dict>
|
|
178
|
+
// Add app groups entitlement
|
|
179
|
+
return entitlements.replace(/<dict>\s*<\/dict>/, `<dict>
|
|
184
180
|
<key>com.apple.security.application-groups</key>
|
|
185
181
|
<array>
|
|
186
182
|
<string>${appGroupId}</string>
|
|
187
183
|
</array>
|
|
188
184
|
</dict>`);
|
|
189
|
-
}
|
|
190
|
-
fs.writeFileSync(entitlementsPath, entitlements);
|
|
191
|
-
console.log(`Updated main app entitlements: ${entitlementsPath}`);
|
|
192
185
|
}
|
|
193
|
-
function
|
|
194
|
-
const
|
|
186
|
+
function createEntitlementsContent(platform, appGroupId) {
|
|
187
|
+
const entries = [];
|
|
188
|
+
// macOS extensions require app-sandbox to be registered
|
|
189
|
+
if (platform === "macos") {
|
|
190
|
+
entries.push(` <key>com.apple.security.app-sandbox</key>
|
|
191
|
+
<true/>`);
|
|
192
|
+
}
|
|
193
|
+
// App groups only if needed
|
|
194
|
+
if (appGroupId) {
|
|
195
|
+
entries.push(` <key>com.apple.security.application-groups</key>
|
|
196
|
+
<array>
|
|
197
|
+
<string>${appGroupId}</string>
|
|
198
|
+
</array>`);
|
|
199
|
+
}
|
|
200
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
195
201
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
196
202
|
<plist version="1.0">
|
|
197
203
|
<dict>
|
|
198
|
-
|
|
199
|
-
<array>
|
|
200
|
-
<string>${appGroupId}</string>
|
|
201
|
-
</array>
|
|
204
|
+
${entries.join("\n")}
|
|
202
205
|
</dict>
|
|
203
206
|
</plist>`;
|
|
207
|
+
}
|
|
208
|
+
function updateMainAppEntitlements(appleDir, appInfo, platform) {
|
|
209
|
+
const platformSuffix = platform === "ios" ? "iOS" : "macOS";
|
|
210
|
+
const targetName = `${appInfo.productName}_${platformSuffix}`;
|
|
211
|
+
const entitlementsPath = path.join(appleDir, targetName, `${targetName}.entitlements`);
|
|
212
|
+
const appGroupId = `group.${appInfo.identifier}`;
|
|
213
|
+
let entitlements;
|
|
214
|
+
if (fs.existsSync(entitlementsPath)) {
|
|
215
|
+
entitlements = fs.readFileSync(entitlementsPath, "utf8");
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
entitlements = EMPTY_ENTITLEMENTS;
|
|
219
|
+
}
|
|
220
|
+
entitlements = addAppGroupToEntitlements(entitlements, appGroupId);
|
|
221
|
+
fs.writeFileSync(entitlementsPath, entitlements);
|
|
222
|
+
console.log(`Updated main app entitlements: ${entitlementsPath}`);
|
|
223
|
+
}
|
|
224
|
+
function createExtensionEntitlements(extensionDir, appGroupId, platform) {
|
|
225
|
+
const entitlements = createEntitlementsContent(platform, appGroupId);
|
|
204
226
|
const entitlementsPath = path.join(extensionDir, `${path.basename(extensionDir)}.entitlements`);
|
|
205
227
|
fs.writeFileSync(entitlementsPath, entitlements);
|
|
206
228
|
return entitlementsPath;
|
|
@@ -251,7 +273,7 @@ const shareExtension = {
|
|
|
251
273
|
extensionName(appInfo) {
|
|
252
274
|
return `${appInfo.productName}-ShareExtension`;
|
|
253
275
|
},
|
|
254
|
-
createFiles(appleDir, appInfo, templatesDir) {
|
|
276
|
+
createFiles(appleDir, appInfo, templatesDir, platform) {
|
|
255
277
|
const extensionDir = path.join(appleDir, "ShareExtension");
|
|
256
278
|
const appGroupId = `group.${appInfo.identifier}`;
|
|
257
279
|
const urlScheme = appInfo.productName
|
|
@@ -341,19 +363,22 @@ const shareExtension = {
|
|
|
341
363
|
fs.writeFileSync(path.join(extensionDir, "Info.plist"), defaultInfoPlist);
|
|
342
364
|
}
|
|
343
365
|
// Create entitlements
|
|
344
|
-
createExtensionEntitlements(extensionDir, appGroupId);
|
|
366
|
+
createExtensionEntitlements(extensionDir, appGroupId, platform);
|
|
345
367
|
console.log(`Created ShareExtension files in ${extensionDir}`);
|
|
346
368
|
},
|
|
347
|
-
updateProjectYml(projectYml, appInfo) {
|
|
369
|
+
updateProjectYml(projectYml, appInfo, platform) {
|
|
348
370
|
const extensionName = this.extensionName(appInfo);
|
|
349
371
|
const extensionBundleId = `${appInfo.identifier}.ShareExtension`;
|
|
350
|
-
const
|
|
372
|
+
const platformSuffix = platform === "ios" ? "iOS" : "macOS";
|
|
373
|
+
const platformValue = platform === "ios" ? "iOS" : "macOS";
|
|
374
|
+
const deploymentTarget = platform === "ios" ? "14.0" : "11.0";
|
|
375
|
+
const targetName = `${appInfo.productName}_${platformSuffix}`;
|
|
351
376
|
// Create the extension target YAML
|
|
352
377
|
const extensionTarget = `
|
|
353
378
|
${extensionName}:
|
|
354
379
|
type: app-extension
|
|
355
|
-
platform:
|
|
356
|
-
deploymentTarget: "
|
|
380
|
+
platform: ${platformValue}
|
|
381
|
+
deploymentTarget: "${deploymentTarget}"
|
|
357
382
|
sources:
|
|
358
383
|
- path: ShareExtension
|
|
359
384
|
info:
|
|
@@ -402,6 +427,7 @@ const EXTENSIONS = {
|
|
|
402
427
|
share: shareExtension,
|
|
403
428
|
};
|
|
404
429
|
function resolveTemplatesDir(options, extensionType) {
|
|
430
|
+
const { platform } = options;
|
|
405
431
|
// Option 1: Explicit templates path
|
|
406
432
|
if (options.templates) {
|
|
407
433
|
const templatesPath = path.resolve(process.cwd(), options.templates);
|
|
@@ -421,16 +447,20 @@ function resolveTemplatesDir(options, extensionType) {
|
|
|
421
447
|
if (extensionConfig.type !== extensionType) {
|
|
422
448
|
throw new Error(`Plugin ${options.plugin} is for '${extensionConfig.type}' extension, not '${extensionType}'`);
|
|
423
449
|
}
|
|
424
|
-
const
|
|
450
|
+
const platformTemplates = extensionConfig.templates[platform];
|
|
451
|
+
if (!platformTemplates) {
|
|
452
|
+
throw new Error(`Plugin ${options.plugin} does not support ${platform} platform`);
|
|
453
|
+
}
|
|
454
|
+
const templatesPath = path.join(pluginPath, platformTemplates);
|
|
425
455
|
if (!fs.existsSync(templatesPath)) {
|
|
426
456
|
throw new Error(`Plugin templates directory not found: ${templatesPath}`);
|
|
427
457
|
}
|
|
428
458
|
return templatesPath;
|
|
429
459
|
}
|
|
430
|
-
// Option 3: Default templates (from bundled dist/cli.js -> ../templates)
|
|
431
|
-
const defaultTemplates = path.join(__dirname$1, "../templates", extensionType);
|
|
460
|
+
// Option 3: Default templates (from bundled dist/cli.js -> ../templates/{platform}/{type})
|
|
461
|
+
const defaultTemplates = path.join(__dirname$1, "../templates", platform, extensionType);
|
|
432
462
|
if (!fs.existsSync(defaultTemplates)) {
|
|
433
|
-
throw new Error(`No templates found. Use --plugin or --templates to specify templates.`);
|
|
463
|
+
throw new Error(`No templates found for ${platform}/${extensionType}. Use --plugin or --templates to specify templates.`);
|
|
434
464
|
}
|
|
435
465
|
return defaultTemplates;
|
|
436
466
|
}
|
|
@@ -448,7 +478,9 @@ function resolvePluginPath(pluginName) {
|
|
|
448
478
|
throw new Error(`Plugin ${pluginName} not found in node_modules. Make sure it's installed.`);
|
|
449
479
|
}
|
|
450
480
|
async function addExtension(type, options) {
|
|
451
|
-
|
|
481
|
+
const { platform } = options;
|
|
482
|
+
const platformDisplay = platform === "ios" ? "iOS" : "macOS";
|
|
483
|
+
console.log(`\nTauri Apple Extensions - Add ${type} (${platformDisplay})\n`);
|
|
452
484
|
try {
|
|
453
485
|
// Validate extension type
|
|
454
486
|
const extension = EXTENSIONS[type];
|
|
@@ -460,7 +492,7 @@ async function addExtension(type, options) {
|
|
|
460
492
|
const projectRoot = findProjectRoot();
|
|
461
493
|
console.log(`Project root: ${projectRoot}`);
|
|
462
494
|
const tauriConfig = findTauriConfig(projectRoot);
|
|
463
|
-
const appleDir = findAppleProjectDir(projectRoot);
|
|
495
|
+
const appleDir = findAppleProjectDir(projectRoot, platform);
|
|
464
496
|
console.log(`Apple project dir: ${appleDir}`);
|
|
465
497
|
// Get app info
|
|
466
498
|
let projectYml = readProjectYml(appleDir);
|
|
@@ -480,12 +512,12 @@ async function addExtension(type, options) {
|
|
|
480
512
|
console.log(`\nUsing templates from: ${templatesDir}`);
|
|
481
513
|
// Run extension setup
|
|
482
514
|
console.log(`\n1. Creating ${extension.displayName} files...`);
|
|
483
|
-
extension.createFiles(appleDir, appInfo, templatesDir);
|
|
515
|
+
extension.createFiles(appleDir, appInfo, templatesDir, platform);
|
|
484
516
|
console.log(`\n2. Updating main app entitlements...`);
|
|
485
|
-
updateMainAppEntitlements(appleDir, appInfo);
|
|
517
|
+
updateMainAppEntitlements(appleDir, appInfo, platform);
|
|
486
518
|
console.log(`\n3. Updating project.yml (extension target + URL scheme)...`);
|
|
487
519
|
projectYml = readProjectYml(appleDir);
|
|
488
|
-
projectYml = extension.updateProjectYml(projectYml, appInfo);
|
|
520
|
+
projectYml = extension.updateProjectYml(projectYml, appInfo, platform);
|
|
489
521
|
writeProjectYml(appleDir, projectYml);
|
|
490
522
|
console.log(`\n4. Regenerating Xcode project...`);
|
|
491
523
|
runXcodeGen(appleDir);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type Platform = "ios" | "macos";
|
|
1
2
|
export interface AppInfo {
|
|
2
3
|
productName: string;
|
|
3
4
|
bundleIdPrefix: string;
|
|
@@ -18,8 +19,8 @@ export interface Extension {
|
|
|
18
19
|
extensionSuffix: string;
|
|
19
20
|
extensionPointIdentifier: string;
|
|
20
21
|
extensionName(appInfo: AppInfo): string;
|
|
21
|
-
createFiles(appleDir: string, appInfo: AppInfo, templatesDir: string): void;
|
|
22
|
-
updateProjectYml(projectYml: string, appInfo: AppInfo): string;
|
|
22
|
+
createFiles(appleDir: string, appInfo: AppInfo, templatesDir: string, platform: Platform): void;
|
|
23
|
+
updateProjectYml(projectYml: string, appInfo: AppInfo, platform: Platform): string;
|
|
23
24
|
}
|
|
24
25
|
export interface TargetConfig {
|
|
25
26
|
name: string;
|
|
@@ -31,5 +32,6 @@ export interface DependencyConfig {
|
|
|
31
32
|
export interface AddOptions {
|
|
32
33
|
plugin?: string;
|
|
33
34
|
templates?: string;
|
|
35
|
+
platform: Platform;
|
|
34
36
|
}
|
|
35
37
|
export type TemplateVariables = Record<string, string>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@choochmeque/tauri-apple-extensions",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Add iOS extensions to Tauri apps with a single command",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Add iOS and macOS extensions to Tauri apps with a single command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -36,8 +36,10 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
39
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
39
40
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
40
41
|
"@types/node": "^25.0.3",
|
|
42
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
41
43
|
"eslint": "^9.0.0",
|
|
42
44
|
"prettier": "^3.0.0",
|
|
43
45
|
"rollup": "^4.0.0",
|
|
@@ -54,6 +56,7 @@
|
|
|
54
56
|
"pretest": "pnpm build",
|
|
55
57
|
"test": "vitest run",
|
|
56
58
|
"test:watch": "vitest",
|
|
59
|
+
"test:coverage": "vitest run --coverage",
|
|
57
60
|
"lint": "eslint .",
|
|
58
61
|
"format": "prettier --write \"./**/*.{cjs,mjs,js,jsx,mts,ts,tsx,html,css,json}\"",
|
|
59
62
|
"format:check": "prettier --check \"./**/*.{cjs,mjs,js,jsx,mts,ts,tsx,html,css,json}\""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
7
|
+
<key>CFBundleDisplayName</key>
|
|
8
|
+
<string>Share</string>
|
|
9
|
+
<key>CFBundleExecutable</key>
|
|
10
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
11
|
+
<key>CFBundleIdentifier</key>
|
|
12
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
13
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
14
|
+
<string>6.0</string>
|
|
15
|
+
<key>CFBundleName</key>
|
|
16
|
+
<string>$(PRODUCT_NAME)</string>
|
|
17
|
+
<key>CFBundlePackageType</key>
|
|
18
|
+
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
19
|
+
<key>CFBundleShortVersionString</key>
|
|
20
|
+
<string>{{VERSION}}</string>
|
|
21
|
+
<key>CFBundleVersion</key>
|
|
22
|
+
<string>{{VERSION}}</string>
|
|
23
|
+
<key>NSExtension</key>
|
|
24
|
+
<dict>
|
|
25
|
+
<key>NSExtensionAttributes</key>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>NSExtensionActivationRule</key>
|
|
28
|
+
<dict>
|
|
29
|
+
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
|
30
|
+
<integer>10</integer>
|
|
31
|
+
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
|
32
|
+
<integer>10</integer>
|
|
33
|
+
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
|
34
|
+
<integer>10</integer>
|
|
35
|
+
<key>NSExtensionActivationSupportsText</key>
|
|
36
|
+
<true/>
|
|
37
|
+
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
|
38
|
+
<integer>1</integer>
|
|
39
|
+
</dict>
|
|
40
|
+
</dict>
|
|
41
|
+
<key>NSExtensionPointIdentifier</key>
|
|
42
|
+
<string>com.apple.share-services</string>
|
|
43
|
+
<key>NSExtensionPrincipalClass</key>
|
|
44
|
+
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
|
|
45
|
+
</dict>
|
|
46
|
+
</dict>
|
|
47
|
+
</plist>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import Social
|
|
3
|
+
import UniformTypeIdentifiers
|
|
4
|
+
|
|
5
|
+
class ShareViewController: NSViewController {
|
|
6
|
+
|
|
7
|
+
// MARK: - Configuration
|
|
8
|
+
private let appGroupIdentifier = "{{APP_GROUP_IDENTIFIER}}"
|
|
9
|
+
private let appURLScheme = "{{APP_URL_SCHEME}}"
|
|
10
|
+
|
|
11
|
+
// MARK: - Lifecycle
|
|
12
|
+
|
|
13
|
+
override func loadView() {
|
|
14
|
+
self.view = NSView(frame: NSRect(x: 0, y: 0, width: 400, height: 300))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override func viewDidLoad() {
|
|
18
|
+
super.viewDidLoad()
|
|
19
|
+
// TODO: Setup your UI here
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override func viewDidAppear() {
|
|
23
|
+
super.viewDidAppear()
|
|
24
|
+
processSharedItems()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// MARK: - Share Processing
|
|
28
|
+
|
|
29
|
+
private func processSharedItems() {
|
|
30
|
+
guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
|
|
31
|
+
complete()
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for item in extensionItems {
|
|
36
|
+
guard let attachments = item.attachments else { continue }
|
|
37
|
+
|
|
38
|
+
for attachment in attachments {
|
|
39
|
+
// TODO: Handle different content types
|
|
40
|
+
// Examples:
|
|
41
|
+
// - UTType.image.identifier for images
|
|
42
|
+
// - UTType.url.identifier for URLs
|
|
43
|
+
// - UTType.text.identifier for text
|
|
44
|
+
// - UTType.fileURL.identifier for files
|
|
45
|
+
|
|
46
|
+
if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
|
|
47
|
+
attachment.loadItem(forTypeIdentifier: UTType.url.identifier) { [weak self] item, error in
|
|
48
|
+
if let url = item as? URL {
|
|
49
|
+
// TODO: Process the URL
|
|
50
|
+
print("Received URL: \(url)")
|
|
51
|
+
}
|
|
52
|
+
DispatchQueue.main.async {
|
|
53
|
+
self?.complete()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if attachment.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
|
|
60
|
+
attachment.loadItem(forTypeIdentifier: UTType.text.identifier) { [weak self] item, error in
|
|
61
|
+
if let text = item as? String {
|
|
62
|
+
// TODO: Process the text
|
|
63
|
+
print("Received text: \(text)")
|
|
64
|
+
}
|
|
65
|
+
DispatchQueue.main.async {
|
|
66
|
+
self?.complete()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
|
|
73
|
+
attachment.loadItem(forTypeIdentifier: UTType.image.identifier) { [weak self] item, error in
|
|
74
|
+
if let url = item as? URL {
|
|
75
|
+
// TODO: Process image file URL
|
|
76
|
+
print("Received image: \(url)")
|
|
77
|
+
} else if let image = item as? NSImage {
|
|
78
|
+
// TODO: Process NSImage directly
|
|
79
|
+
print("Received NSImage")
|
|
80
|
+
}
|
|
81
|
+
DispatchQueue.main.async {
|
|
82
|
+
self?.complete()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
complete()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// MARK: - App Group Storage (Optional)
|
|
94
|
+
|
|
95
|
+
/// Save data to App Group for main app to read
|
|
96
|
+
private func saveToAppGroup(_ data: Data, forKey key: String) -> Bool {
|
|
97
|
+
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
|
98
|
+
print("App Groups not configured")
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
userDefaults.set(data, forKey: key)
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Get App Group container URL for file storage
|
|
106
|
+
private func appGroupContainerURL() -> URL? {
|
|
107
|
+
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// MARK: - Open Main App (Optional)
|
|
111
|
+
|
|
112
|
+
private func openMainApp() {
|
|
113
|
+
guard let url = URL(string: "\(appURLScheme)://share") else { return }
|
|
114
|
+
NSWorkspace.shared.open(url)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MARK: - Complete
|
|
118
|
+
|
|
119
|
+
private func complete() {
|
|
120
|
+
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
File without changes
|
|
File without changes
|