Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "expo-share-content",
"version": "0.1.0",
"version": "0.1.1",
"description": "Receive text, URLs, images, videos, audio, and files shared to an Expo app.",
"type": "commonjs",
"main": "build/index.js",
Expand Down
8 changes: 5 additions & 3 deletions plugin/src/__tests__/ios-artifacts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ describe('iOS Share Extension artifacts', () => {
expect(swift).toContain('completeRequest(returningItems: nil, completionHandler: nil)');
expect(swift).toContain('loadFileRepresentation(forTypeIdentifier:');
expect(swift).toContain('read(upToCount:');
// Binary/media attachments must stream. Text/URL may load small in-memory values,
// but only after a byte-length guard (loadDataRepresentation path).
expect(swift).toContain('loadDataRepresentation(forTypeIdentifier:');
// Binary/media attachments must stream. Text/URL use typed object loading so
// ExtensionKit receives an expected value class for cross-process providers.
expect(swift).toContain('loadObject(ofClass: URL.self)');
expect(swift).toContain('loadObject(ofClass: String.self)');
expect(swift).not.toContain('loadDataRepresentation(forTypeIdentifier:');
expect(swift).not.toMatch(/loadItem\(forTypeIdentifier:\s*UTType\.(image|movie|audio|fileURL)/);
expect(swift).not.toContain('image.pngData()');
expect(swift).not.toContain('copyItem(at: source');
Expand Down
92 changes: 42 additions & 50 deletions plugin/src/ios/ShareViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -280,76 +280,68 @@ final class ShareViewController: UIViewController {
return try await loadFile(provider: provider, typeIdentifier: UTType.fileURL.identifier, itemType: "file", id: id, shareId: shareId)
}
if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
return try await loadBoundedText(
provider: provider,
typeIdentifier: UTType.url.identifier,
id: id,
asURL: true
)
return try await loadURL(provider: provider, id: id)
}
if provider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
return try await loadBoundedText(
provider: provider,
typeIdentifier: UTType.text.identifier,
id: id,
asURL: false
)
return try await loadText(provider: provider, id: id)
}
guard let typeIdentifier = provider.registeredTypeIdentifiers.first else { return nil }
return try await loadFile(provider: provider, typeIdentifier: typeIdentifier, itemType: "file", id: id, shareId: shareId)
}

private func loadBoundedText(
provider: NSItemProvider,
typeIdentifier: String,
id: String,
asURL: Bool
) async throws -> SharedItem {
let data = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Data, Error>) in
provider.loadDataRepresentation(forTypeIdentifier: typeIdentifier) { data, error in
private func loadURL(provider: NSItemProvider, id: String) async throws -> SharedItem {
let url = try await withCheckedThrowingContinuation {
(continuation: CheckedContinuation<URL, Error>) in
_ = provider.loadObject(ofClass: URL.self) { url, error in
if let error {
continuation.resume(throwing: error)
return
}
guard let data else {
guard let url else {
continuation.resume(throwing: ShareExtensionError.itemUnavailable)
return
}
guard data.count <= self.maxTextBytes else {
continuation.resume(throwing: ShareExtensionError.textTooLarge(self.maxTextBytes))
return
}
continuation.resume(returning: data)
continuation.resume(returning: url)
}
}

if asURL {
guard
let text = String(data: data, encoding: .utf8) ?? String(data: data, encoding: .utf16),
let url = URL(string: text.trimmingCharacters(in: .whitespacesAndNewlines)),
let scheme = url.scheme?.lowercased(),
["http", "https"].contains(scheme)
else {
throw ShareExtensionError.itemUnavailable
}
let normalized = url.absoluteString
guard normalized.utf8.count <= maxTextBytes else {
throw ShareExtensionError.textTooLarge(maxTextBytes)
}
return SharedItem(
id: id,
type: "url",
mimeType: "text/uri-list",
text: normalized,
uri: nil,
fileName: nil,
size: nil
)
guard
let scheme = url.scheme?.lowercased(),
["http", "https"].contains(scheme)
else {
throw ShareExtensionError.itemUnavailable
}
let normalized = url.absoluteString
guard normalized.utf8.count <= maxTextBytes else {
throw ShareExtensionError.textTooLarge(maxTextBytes)
}
return SharedItem(
id: id,
type: "url",
mimeType: "text/uri-list",
text: normalized,
uri: nil,
fileName: nil,
size: nil
)
}

guard let value = String(data: data, encoding: .utf8) ?? String(data: data, encoding: .utf16) else {
throw ShareExtensionError.itemUnavailable
private func loadText(provider: NSItemProvider, id: String) async throws -> SharedItem {
let value = try await withCheckedThrowingContinuation {
(continuation: CheckedContinuation<String, Error>) in
_ = provider.loadObject(ofClass: String.self) { value, error in
if let error {
continuation.resume(throwing: error)
return
}
guard let value else {
continuation.resume(throwing: ShareExtensionError.itemUnavailable)
return
}
continuation.resume(returning: value)
}
}

guard value.utf8.count <= maxTextBytes else {
throw ShareExtensionError.textTooLarge(maxTextBytes)
}
Expand Down
Loading