@choochmeque/tauri-apple-extensions 0.1.0
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 +21 -0
- package/README.md +90 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +536 -0
- package/dist/commands/add.d.ts +2 -0
- package/dist/core/entitlements.d.ts +3 -0
- package/dist/core/info-plist.d.ts +2 -0
- package/dist/core/project-discovery.d.ts +5 -0
- package/dist/core/project-yml.d.ts +5 -0
- package/dist/core/xcodegen.d.ts +1 -0
- package/dist/extensions/share.d.ts +2 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +523 -0
- package/dist/types.d.ts +35 -0
- package/dist/utils/plist.d.ts +11 -0
- package/dist/utils/template.d.ts +14 -0
- package/dist/utils/yaml-simple.d.ts +5 -0
- package/package.json +64 -0
- package/templates/share/Info.plist +47 -0
- package/templates/share/ShareViewController.swift +246 -0
|
@@ -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,246 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import Social
|
|
3
|
+
import MobileCoreServices
|
|
4
|
+
import UniformTypeIdentifiers
|
|
5
|
+
|
|
6
|
+
class ShareViewController: UIViewController {
|
|
7
|
+
|
|
8
|
+
// MARK: - Configuration (will be replaced by setup script)
|
|
9
|
+
private let appGroupIdentifier = "{{APP_GROUP_IDENTIFIER}}"
|
|
10
|
+
private let appURLScheme = "{{APP_URL_SCHEME}}"
|
|
11
|
+
|
|
12
|
+
override func viewDidLoad() {
|
|
13
|
+
super.viewDidLoad()
|
|
14
|
+
view.backgroundColor = .clear
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override func viewDidAppear(_ animated: Bool) {
|
|
18
|
+
super.viewDidAppear(animated)
|
|
19
|
+
handleSharedContent()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private func handleSharedContent() {
|
|
23
|
+
// Check App Groups configuration early
|
|
24
|
+
if FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) == nil {
|
|
25
|
+
showError("App Groups not configured.\n\nPlease enable 'App Groups' capability in Xcode for both the main app and ShareExtension targets, and configure '\(appGroupIdentifier)' in Apple Developer Portal.")
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
|
|
30
|
+
completeRequest()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Use a serial queue to safely collect results
|
|
35
|
+
let resultQueue = DispatchQueue(label: "sharekit.results")
|
|
36
|
+
var sharedContent: [String: Any] = [:]
|
|
37
|
+
var files: [[String: Any]] = []
|
|
38
|
+
var textContent: String? = nil
|
|
39
|
+
|
|
40
|
+
let group = DispatchGroup()
|
|
41
|
+
|
|
42
|
+
for extensionItem in extensionItems {
|
|
43
|
+
guard let attachments = extensionItem.attachments else { continue }
|
|
44
|
+
|
|
45
|
+
for attachment in attachments {
|
|
46
|
+
group.enter()
|
|
47
|
+
|
|
48
|
+
if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
|
|
49
|
+
// Check image FIRST (before URL) because images can also be URLs
|
|
50
|
+
attachment.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { [weak self] item, error in
|
|
51
|
+
defer { group.leave() }
|
|
52
|
+
if let url = item as? URL {
|
|
53
|
+
if let fileInfo = self?.copyFileToAppGroup(url: url) {
|
|
54
|
+
resultQueue.sync { files.append(fileInfo) }
|
|
55
|
+
}
|
|
56
|
+
} else if let image = item as? UIImage {
|
|
57
|
+
if let fileInfo = self?.saveImageToAppGroup(image: image) {
|
|
58
|
+
resultQueue.sync { files.append(fileInfo) }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} else if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
|
|
63
|
+
attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] item, error in
|
|
64
|
+
defer { group.leave() }
|
|
65
|
+
guard let url = item as? URL else { return }
|
|
66
|
+
|
|
67
|
+
if url.isFileURL {
|
|
68
|
+
if let fileInfo = self?.copyFileToAppGroup(url: url) {
|
|
69
|
+
resultQueue.sync { files.append(fileInfo) }
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
resultQueue.sync { textContent = url.absoluteString }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else if attachment.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
|
|
76
|
+
attachment.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { item, error in
|
|
77
|
+
defer { group.leave() }
|
|
78
|
+
if let text = item as? String {
|
|
79
|
+
resultQueue.sync { textContent = text }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else if attachment.hasItemConformingToTypeIdentifier(UTType.data.identifier) {
|
|
83
|
+
attachment.loadItem(forTypeIdentifier: UTType.data.identifier, options: nil) { [weak self] item, error in
|
|
84
|
+
defer { group.leave() }
|
|
85
|
+
if let url = item as? URL {
|
|
86
|
+
if let fileInfo = self?.copyFileToAppGroup(url: url) {
|
|
87
|
+
resultQueue.sync { files.append(fileInfo) }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
group.leave()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
group.notify(queue: .main) { [weak self] in
|
|
98
|
+
guard let self = self else { return }
|
|
99
|
+
|
|
100
|
+
if !files.isEmpty {
|
|
101
|
+
sharedContent["type"] = "files"
|
|
102
|
+
sharedContent["files"] = files
|
|
103
|
+
} else if let text = textContent {
|
|
104
|
+
sharedContent["type"] = "text"
|
|
105
|
+
sharedContent["text"] = text
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if !sharedContent.isEmpty {
|
|
109
|
+
_ = self.saveToAppGroup(content: sharedContent)
|
|
110
|
+
self.openMainAppAndComplete()
|
|
111
|
+
} else {
|
|
112
|
+
self.completeRequest()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private func showError(_ message: String) {
|
|
118
|
+
let alert = UIAlertController(
|
|
119
|
+
title: "ShareKit Error",
|
|
120
|
+
message: message,
|
|
121
|
+
preferredStyle: .alert
|
|
122
|
+
)
|
|
123
|
+
alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in
|
|
124
|
+
self?.completeRequest()
|
|
125
|
+
})
|
|
126
|
+
present(alert, animated: true)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private func copyFileToAppGroup(url: URL) -> [String: Any]? {
|
|
130
|
+
guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else {
|
|
131
|
+
return nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let sharedFilesDir = containerURL.appendingPathComponent("shared_files", isDirectory: true)
|
|
135
|
+
try? FileManager.default.createDirectory(at: sharedFilesDir, withIntermediateDirectories: true)
|
|
136
|
+
|
|
137
|
+
let fileName = url.lastPathComponent
|
|
138
|
+
let destinationURL = sharedFilesDir.appendingPathComponent(UUID().uuidString + "_" + fileName)
|
|
139
|
+
|
|
140
|
+
do {
|
|
141
|
+
if url.startAccessingSecurityScopedResource() {
|
|
142
|
+
defer { url.stopAccessingSecurityScopedResource() }
|
|
143
|
+
try FileManager.default.copyItem(at: url, to: destinationURL)
|
|
144
|
+
} else {
|
|
145
|
+
try FileManager.default.copyItem(at: url, to: destinationURL)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
var fileInfo: [String: Any] = [
|
|
149
|
+
"path": destinationURL.path,
|
|
150
|
+
"name": fileName
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
if let mimeType = getMimeType(for: url) {
|
|
154
|
+
fileInfo["mimeType"] = mimeType
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if let attributes = try? FileManager.default.attributesOfItem(atPath: destinationURL.path),
|
|
158
|
+
let size = attributes[.size] as? Int64 {
|
|
159
|
+
fileInfo["size"] = size
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return fileInfo
|
|
163
|
+
} catch {
|
|
164
|
+
print("ShareKit: Failed to copy file: \(error)")
|
|
165
|
+
return nil
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private func saveImageToAppGroup(image: UIImage) -> [String: Any]? {
|
|
170
|
+
guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else {
|
|
171
|
+
return nil
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let sharedFilesDir = containerURL.appendingPathComponent("shared_files", isDirectory: true)
|
|
175
|
+
try? FileManager.default.createDirectory(at: sharedFilesDir, withIntermediateDirectories: true)
|
|
176
|
+
|
|
177
|
+
let fileName = UUID().uuidString + ".png"
|
|
178
|
+
let destinationURL = sharedFilesDir.appendingPathComponent(fileName)
|
|
179
|
+
|
|
180
|
+
guard let data = image.pngData() else { return nil }
|
|
181
|
+
|
|
182
|
+
do {
|
|
183
|
+
try data.write(to: destinationURL)
|
|
184
|
+
|
|
185
|
+
return [
|
|
186
|
+
"path": destinationURL.path,
|
|
187
|
+
"name": fileName,
|
|
188
|
+
"mimeType": "image/png",
|
|
189
|
+
"size": data.count
|
|
190
|
+
]
|
|
191
|
+
} catch {
|
|
192
|
+
print("ShareKit: Failed to save image: \(error)")
|
|
193
|
+
return nil
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private func getMimeType(for url: URL) -> String? {
|
|
198
|
+
if let uti = UTType(filenameExtension: url.pathExtension) {
|
|
199
|
+
return uti.preferredMIMEType
|
|
200
|
+
}
|
|
201
|
+
return nil
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private func saveToAppGroup(content: [String: Any]) -> Bool {
|
|
205
|
+
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
|
206
|
+
showError("App Groups not configured.\n\nPlease enable 'App Groups' capability in Xcode for both the main app and ShareExtension targets, and configure '\(appGroupIdentifier)' in Apple Developer Portal.")
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
do {
|
|
211
|
+
let data = try JSONSerialization.data(withJSONObject: content)
|
|
212
|
+
userDefaults.set(data, forKey: "pendingSharedContent")
|
|
213
|
+
userDefaults.synchronize()
|
|
214
|
+
return true
|
|
215
|
+
} catch {
|
|
216
|
+
showError("Failed to save shared content: \(error.localizedDescription)")
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private func openMainAppAndComplete() {
|
|
222
|
+
guard let url = URL(string: "\(appURLScheme)://sharekit-content") else {
|
|
223
|
+
completeRequest()
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var responder: UIResponder? = self
|
|
228
|
+
while responder != nil {
|
|
229
|
+
if let application = responder as? UIApplication {
|
|
230
|
+
if #available(iOS 18.0, *) {
|
|
231
|
+
application.open(url, options: [:], completionHandler: nil)
|
|
232
|
+
} else {
|
|
233
|
+
_ = application.perform(NSSelectorFromString("openURL:"), with: url)
|
|
234
|
+
}
|
|
235
|
+
break
|
|
236
|
+
}
|
|
237
|
+
responder = responder?.next
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
completeRequest()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private func completeRequest() {
|
|
244
|
+
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
|
245
|
+
}
|
|
246
|
+
}
|