feat: You can now share files & text from other apps into SN on iOS (#2358)

This commit is contained in:
Aman Harwara
2023-07-15 15:05:49 +05:30
committed by GitHub
parent 61043aee65
commit 88ef5887a9
12 changed files with 930 additions and 40 deletions

View File

@@ -0,0 +1,14 @@
#import <React/RCTBridgeModule.h>
#import <Photos/Photos.h>
@interface RCT_EXTERN_MODULE(ReceiveSharingIntent, NSObject)
RCT_EXTERN_METHOD(getFileNames:(NSString)url
resolver:(RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject);
RCT_EXTERN_METHOD(clearFileNames)
@end

View File

@@ -0,0 +1,147 @@
import Foundation
import Photos
import MobileCoreServices
@objc(ReceiveSharingIntent)
class ReceiveSharingIntent: NSObject {
struct Share: Codable {
var media: [SharedMediaFile] = []
var text: [String] = []
var urls: [String] = []
}
private var share = Share()
@objc
func getFileNames(_ url: String,
resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) -> Void {
let fileUrl = URL(string: url)
let json = handleUrl(url: fileUrl);
if(json == "error"){
let error = NSError(domain: "", code: 400, userInfo: nil)
reject("message", "file type is Invalid", error);
}else if(json == "invalid group name"){
let error = NSError(domain: "", code: 400, userInfo: nil)
reject("message", "invalid group name. Please check your share extention bundle name is same as `group.mainbundle name` ", error);
}else{
resolve(json);
}
}
private func handleUrl(url: URL?) -> String? {
if let url = url {
let appDomain = Bundle.main.bundleIdentifier!
let userDefaults = UserDefaults(suiteName: "group.\(appDomain)")
if let key = url.host?.components(separatedBy: "=").last {
if let mediaJson = userDefaults?.object(forKey: "\(key).media") as? Data {
let mediaSharedArray = decode(data: mediaJson)
let sharedMediaFiles: [SharedMediaFile] = mediaSharedArray.compactMap {
guard let path = getAbsolutePath(for: $0.path) else {
return nil
}
return SharedMediaFile.init(path: path, fileName: fileNameForPath(path: path), mimeType: mimeTypeForPath(path: path))
}
self.share.media = sharedMediaFiles
}
if let textSharedArray = userDefaults?.object(forKey: "\(key).text") as? [String] {
self.share.text = textSharedArray
}
if let textSharedArray = userDefaults?.object(forKey: "\(key).url") as? [String] {
self.share.urls = textSharedArray
}
let encodedData = try? JSONEncoder().encode(self.share)
let json = String(data: encodedData!, encoding: .utf8)!
return json
}
return "error"
}
return "invalid group name"
}
private func getAbsolutePath(for identifier: String) -> String? {
if (identifier.starts(with: "file://") || identifier.starts(with: "/var/mobile/Media") || identifier.starts(with: "/private/var/mobile")) {
return identifier;
}
let phAsset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: .none).firstObject
if(phAsset == nil) {
return nil
}
let (url, _) = getFullSizeImageURLAndOrientation(for: phAsset!)
return url
}
private func getFullSizeImageURLAndOrientation(for asset: PHAsset)-> (String?, Int) {
var url: String? = nil
var orientation: Int = 0
let semaphore = DispatchSemaphore(value: 0)
let options2 = PHContentEditingInputRequestOptions()
options2.isNetworkAccessAllowed = true
asset.requestContentEditingInput(with: options2){(input, info) in
orientation = Int(input?.fullSizeImageOrientation ?? 0)
url = input?.fullSizeImageURL?.path
semaphore.signal()
}
semaphore.wait()
return (url, orientation)
}
private func decode(data: Data) -> [SharedMediaFile] {
let encodedData = try? JSONDecoder().decode([SharedMediaFile].self, from: data)
return encodedData!
}
private func toJson(data: [SharedMediaFile]?) -> String? {
if data == nil {
return nil
}
let encodedData = try? JSONEncoder().encode(data)
let json = String(data: encodedData!, encoding: .utf8)!
return json
}
class SharedMediaFile: Codable {
var path: String;
var fileName: String?;
var mimeType: String?;
init(path: String, fileName: String?, mimeType: String?) {
self.path = path
self.fileName = fileName
self.mimeType = mimeType
}
}
@objc
func clearFileNames(){
print("clearFileNames");
}
@objc
static func requiresMainQueueSetup() -> Bool {
return true
}
}
func fileNameForPath(path: String) -> String? {
let url = NSURL(fileURLWithPath: path)
return url.lastPathComponent
}
func mimeTypeForPath(path: String) -> String {
let url = NSURL(fileURLWithPath: path)
let pathExtension = url.pathExtension
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension! as NSString, nil)?.takeRetainedValue() {
if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
return mimetype as String
}
return "application/octet-stream"
}
return "application/octet-stream"
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>PHSupportedMediaTypes</key>
<array>
<string>Video</string>
<string>Image</string>
</array>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>100</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.standardnotes.standardnotes</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,336 @@
import UIKit
import Social
import MobileCoreServices
import Photos
import UniformTypeIdentifiers
let hostAppBundleIdentifier = "com.standardnotes.standardnotes"
let shareProtocol = hostAppBundleIdentifier
class ShareViewController: SLComposeServiceViewController {
let sharedKey = "ShareKey"
var sharedMedia: [SharedMediaFile] = []
var sharedText: [String] = []
var sharedURL: [String] = []
let imageContentType = UTType.image.identifier
let videoContentType = UTType.movie.identifier
let textContentType = UTType.text.identifier
let urlContentType = UTType.url.identifier
let fileURLType = UTType.fileURL.identifier;
override func isContentValid() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad();
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
if let contents = content.attachments {
for (index, attachment) in (contents).enumerated() {
if attachment.hasItemConformingToTypeIdentifier(fileURLType) {
handleFiles(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(imageContentType) {
handleImages(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
handleText(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
handleUrl(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(videoContentType) {
handleVideos(content: content, attachment: attachment, index: index)
}
}
}
}
}
override func didSelectPost() {
print("didSelectPost");
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
private func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in
if error == nil, let item = data as? String, let this = self {
this.sharedText.append(item)
if index == (content.attachments?.count)! - 1 {
this.redirectToHostApp()
}
} else {
self?.dismissWithError()
}
}
}
private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in
if error == nil, let item = data as? URL, let this = self {
this.sharedURL.append(item.absoluteString)
if index == (content.attachments?.count)! - 1 {
this.redirectToHostApp()
}
} else {
self?.dismissWithError()
}
}
}
private func handleImages (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in
if error == nil, let url = data as? URL, let this = self {
// this.redirectToHostApp(type: .media)
// Always copy
let fileExtension = this.getExtension(from: url, type: .video)
let newName = UUID().uuidString
let newPath = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
.appendingPathComponent("\(newName).\(fileExtension)")
let copied = this.copyFile(at: url, to: newPath)
if(copied) {
this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image))
}
if index == (content.attachments?.count)! - 1 {
this.redirectToHostApp()
}
} else {
self?.dismissWithError()
}
}
}
private func handleVideos (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: videoContentType, options:nil) { [weak self] data, error in
if error == nil, let url = data as? URL, let this = self {
// Always copy
let fileExtension = this.getExtension(from: url, type: .video)
let newName = UUID().uuidString
let newPath = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
.appendingPathComponent("\(newName).\(fileExtension)")
let copied = this.copyFile(at: url, to: newPath)
if(copied) {
guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else {
return
}
this.sharedMedia.append(sharedFile)
}
if index == (content.attachments?.count)! - 1 {
this.redirectToHostApp()
}
} else {
self?.dismissWithError()
}
}
}
private func handleFiles (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: fileURLType, options: nil) { [weak self] data, error in
if error == nil, let url = data as? URL, let this = self {
// Always copy
let newName = this.getFileName(from :url)
let newPath = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
.appendingPathComponent("\(newName)")
let copied = this.copyFile(at: url, to: newPath)
if (copied) {
this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .file))
}
if index == (content.attachments?.count)! - 1 {
this.redirectToHostApp()
}
} else {
self?.dismissWithError()
}
}
}
private func redirectToHostApp() {
let userDefaults = UserDefaults(suiteName: "group.\(hostAppBundleIdentifier)")
userDefaults?.set(self.toData(data: self.sharedMedia), forKey: "\(self.sharedKey).media")
userDefaults?.set(self.sharedText, forKey: "\(self.sharedKey).text")
userDefaults?.set(self.sharedURL, forKey: "\(self.sharedKey).url")
userDefaults?.synchronize()
let url = URL(string: "\(shareProtocol)://dataUrl=\(sharedKey)")
var responder = self as UIResponder?
let selectorOpenURL = sel_registerName("openURL:")
while (responder != nil) {
if (responder?.responds(to: selectorOpenURL))! {
let _ = responder?.perform(selectorOpenURL, with: url)
}
responder = responder!.next
}
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
private func dismissWithError() {
print("[ERROR] Error loading data!")
let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
let action = UIAlertAction(title: "Error", style: .cancel) { _ in
self.dismiss(animated: true, completion: nil)
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
private func alertLog(message: String) {
let alert = UIAlertController(title: "Log", message: message, preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
enum RedirectType {
case media
case text
case file
}
func getExtension(from url: URL, type: SharedMediaType) -> String {
let parts = url.lastPathComponent.components(separatedBy: ".")
var ex: String? = nil
if (parts.count > 1) {
ex = parts.last
}
if (ex == nil) {
switch type {
case .image:
ex = "PNG"
case .video:
ex = "MP4"
case .file:
ex = "TXT"
}
}
return ex ?? "Unknown"
}
func getFileName(from url: URL) -> String {
var name = url.lastPathComponent
if (name == "") {
name = UUID().uuidString + "." + getExtension(from: url, type: .file)
}
return name
}
func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
do {
if FileManager.default.fileExists(atPath: dstURL.path) {
try FileManager.default.removeItem(at: dstURL)
}
try FileManager.default.copyItem(at: srcURL, to: dstURL)
} catch (let error) {
print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
return false
}
return true
}
private func getSharedMediaFile(forVideo: URL) -> SharedMediaFile? {
let asset = AVAsset(url: forVideo)
let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
let thumbnailPath = getThumbnailPath(for: forVideo)
if FileManager.default.fileExists(atPath: thumbnailPath.path) {
return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video)
}
var saved = false
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
// let scale = UIScreen.main.scale
assetImgGenerate.maximumSize = CGSize(width: 360, height: 360)
do {
let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil)
try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath)
saved = true
} catch {
saved = false
}
return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil
}
private func getThumbnailPath(for url: URL) -> URL {
let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "")
let path = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
.appendingPathComponent("\(fileName).jpg")
return path
}
class SharedMediaFile: Codable {
var path: String; // can be image, video or url path. It can also be text content
var thumbnail: String?; // video thumbnail
var duration: Double?; // video duration in milliseconds
var type: SharedMediaType;
init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) {
self.path = path
self.thumbnail = thumbnail
self.duration = duration
self.type = type
}
// Debug method to print out SharedMediaFile details in the console
func toString() {
print("[SharedMediaFile] \n\tpath: \(self.path)\n\tthumbnail: \(self.thumbnail)\n\tduration: \(self.duration)\n\ttype: \(self.type)")
}
}
enum SharedMediaType: Int, Codable {
case image
case video
case file
}
func toData(data: [SharedMediaFile]) -> Data {
let encodedData = try? JSONEncoder().encode(data)
return encodedData!
}
}
extension Array {
subscript (safe index: UInt) -> Element? {
return Int(index) < count ? self[Int(index)] : nil
}
}

View File

@@ -0,0 +1,2 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTViewManager.h>

View File

@@ -8,6 +8,11 @@
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* StandardNotesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* StandardNotesTests.m */; };
07CAB75E2A618128008FE1EF /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CAB75D2A618128008FE1EF /* ShareViewController.swift */; };
07CAB7612A618128008FE1EF /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 07CAB75F2A618128008FE1EF /* MainInterface.storyboard */; };
07CAB7652A618128008FE1EF /* Share To SN.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 07CAB75B2A618128008FE1EF /* Share To SN.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
07CAB76E2A618353008FE1EF /* ReceiveSharingIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CAB76D2A618353008FE1EF /* ReceiveSharingIntent.swift */; };
07CAB7702A618385008FE1EF /* ReceiveSharingIntent.m in Sources */ = {isa = PBXBuildFile; fileRef = 07CAB76F2A618385008FE1EF /* ReceiveSharingIntent.m */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
1C2EEB3B45F4EB07AC795C77 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
@@ -34,13 +39,43 @@
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = StandardNotes;
};
07CAB7632A618128008FE1EF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 07CAB75A2A618128008FE1EF;
remoteInfo = "Share To SN";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
07CAB7662A618128008FE1EF /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
07CAB7652A618128008FE1EF /* Share To SN.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
00E356EE1AD99517003FC87E /* StandardNotesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StandardNotesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* StandardNotesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StandardNotesTests.m; sourceTree = "<group>"; };
04FCB5A3A3387CA3CFC82AA3 /* libPods-StandardNotes-StandardNotesTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-StandardNotes-StandardNotesTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
07CAB75B2A618128008FE1EF /* Share To SN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share To SN.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
07CAB75D2A618128008FE1EF /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
07CAB7602A618128008FE1EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
07CAB7622A618128008FE1EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
07CAB76A2A6182C7008FE1EF /* StandardNotesDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = StandardNotesDebug.entitlements; path = StandardNotes/StandardNotesDebug.entitlements; sourceTree = "<group>"; };
07CAB76B2A6182D6008FE1EF /* Share To SNDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Share To SNDebug.entitlements"; sourceTree = "<group>"; };
07CAB76C2A618353008FE1EF /* StandardNotes-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "StandardNotes-Bridging-Header.h"; sourceTree = "<group>"; };
07CAB76D2A618353008FE1EF /* ReceiveSharingIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReceiveSharingIntent.swift; path = ReceiveSharingIntent/ReceiveSharingIntent.swift; sourceTree = "<group>"; };
07CAB76F2A618385008FE1EF /* ReceiveSharingIntent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ReceiveSharingIntent.m; path = ReceiveSharingIntent/ReceiveSharingIntent.m; sourceTree = "<group>"; };
0BB10C8D896AFACECE748F6D /* Pods-StandardNotesDev.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StandardNotesDev.release.xcconfig"; path = "Target Support Files/Pods-StandardNotesDev/Pods-StandardNotesDev.release.xcconfig"; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* StandardNotes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StandardNotes.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = StandardNotes/AppDelegate.h; sourceTree = "<group>"; };
@@ -75,6 +110,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
07CAB7582A618128008FE1EF /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -113,6 +155,17 @@
name = "Supporting Files";
sourceTree = "<group>";
};
07CAB75C2A618128008FE1EF /* Share To SN */ = {
isa = PBXGroup;
children = (
07CAB76B2A6182D6008FE1EF /* Share To SNDebug.entitlements */,
07CAB75D2A618128008FE1EF /* ShareViewController.swift */,
07CAB75F2A618128008FE1EF /* MainInterface.storyboard */,
07CAB7622A618128008FE1EF /* Info.plist */,
);
path = "Share To SN";
sourceTree = "<group>";
};
0D0B9F98B8FBE9ED9C11F260 /* Pods */ = {
isa = PBXGroup;
children = (
@@ -129,6 +182,7 @@
13B07FAE1A68108700A75B9A /* StandardNotes */ = {
isa = PBXGroup;
children = (
07CAB76A2A6182C7008FE1EF /* StandardNotesDebug.entitlements */,
D261494428699DCE00B17102 /* Web.bundle */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
CDC75795292552080019F4AF /* AppDelegate.mm */,
@@ -137,6 +191,9 @@
13B07FB71A68108700A75B9A /* main.m */,
CD7D5EC8278005B6005FE1BF /* Info.plist */,
CD7D5EC927800608005FE1BF /* LaunchScreen.storyboard */,
07CAB76D2A618353008FE1EF /* ReceiveSharingIntent.swift */,
07CAB76C2A618353008FE1EF /* StandardNotes-Bridging-Header.h */,
07CAB76F2A618385008FE1EF /* ReceiveSharingIntent.m */,
);
name = StandardNotes;
sourceTree = "<group>";
@@ -166,6 +223,7 @@
13B07FAE1A68108700A75B9A /* StandardNotes */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* StandardNotesTests */,
07CAB75C2A618128008FE1EF /* Share To SN */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
CD7D5EE127801645005FE1BF /* StandardNotesDev-Info.plist */,
@@ -184,6 +242,7 @@
13B07F961A680F5B00A75B9A /* StandardNotes.app */,
00E356EE1AD99517003FC87E /* StandardNotesTests.xctest */,
CD7D5EDF278015D2005FE1BF /* StandardNotesDev.app */,
07CAB75B2A618128008FE1EF /* Share To SN.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -212,6 +271,23 @@
productReference = 00E356EE1AD99517003FC87E /* StandardNotesTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
07CAB75A2A618128008FE1EF /* Share To SN */ = {
isa = PBXNativeTarget;
buildConfigurationList = 07CAB7692A618128008FE1EF /* Build configuration list for PBXNativeTarget "Share To SN" */;
buildPhases = (
07CAB7572A618128008FE1EF /* Sources */,
07CAB7582A618128008FE1EF /* Frameworks */,
07CAB7592A618128008FE1EF /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "Share To SN";
productName = "Share To SN";
productReference = 07CAB75B2A618128008FE1EF /* Share To SN.appex */;
productType = "com.apple.product-type.app-extension";
};
13B07F861A680F5B00A75B9A /* StandardNotes */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "StandardNotes" */;
@@ -224,10 +300,12 @@
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
ADF6EC52208176452ADF4217 /* [CP] Embed Pods Frameworks */,
BD9D24718C148A593389F498 /* [CP] Copy Pods Resources */,
07CAB7662A618128008FE1EF /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
07CAB7642A618128008FE1EF /* PBXTargetDependency */,
);
name = StandardNotes;
productName = StandardNotes;
@@ -262,14 +340,18 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1210;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
07CAB75A2A618128008FE1EF = {
CreatedOnToolsVersion = 14.2;
};
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1120;
LastSwiftMigration = 1420;
};
};
};
@@ -289,6 +371,7 @@
13B07F861A680F5B00A75B9A /* StandardNotes */,
00E356ED1AD99517003FC87E /* StandardNotesTests */,
CD7D5ECB278015D2005FE1BF /* StandardNotesDev */,
07CAB75A2A618128008FE1EF /* Share To SN */,
);
};
/* End PBXProject section */
@@ -301,6 +384,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
07CAB7592A618128008FE1EF /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
07CAB7612A618128008FE1EF /* MainInterface.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -570,11 +661,21 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
07CAB7572A618128008FE1EF /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
07CAB75E2A618128008FE1EF /* ShareViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CDC75796292552080019F4AF /* AppDelegate.mm in Sources */,
07CAB76E2A618353008FE1EF /* ReceiveSharingIntent.swift in Sources */,
07CAB7702A618385008FE1EF /* ReceiveSharingIntent.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -596,13 +697,30 @@
target = 13B07F861A680F5B00A75B9A /* StandardNotes */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
07CAB7642A618128008FE1EF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 07CAB75A2A618128008FE1EF /* Share To SN */;
targetProxy = 07CAB7632A618128008FE1EF /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
07CAB75F2A618128008FE1EF /* MainInterface.storyboard */ = {
isa = PBXVariantGroup;
children = (
07CAB7602A618128008FE1EF /* Base */,
);
name = MainInterface.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5915B3231CE7C52662278306 /* Pods-StandardNotes-StandardNotesTests.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -630,6 +748,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 321F15E603CF0AF8B1769447 /* Pods-StandardNotes-StandardNotesTests.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
INFOPLIST_FILE = StandardNotesTests/Info.plist;
@@ -650,13 +769,93 @@
};
name = Release;
};
07CAB7672A618128008FE1EF /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "Share To SN/Share To SNDebug.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = HKF9BXSN95;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Share To SN/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Share To SN";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.standardnotes.standardnotes.Share-To-SN";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
07CAB7682A618128008FE1EF /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = HKF9BXSN95;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Share To SN/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Share To SN";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.standardnotes.standardnotes.Share-To-SN";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 66417CEB7622E77D89928FCA /* Pods-StandardNotes.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = Blue;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = StandardNotes/StandardNotes.entitlements;
CODE_SIGN_ENTITLEMENTS = StandardNotes/StandardNotesDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -676,6 +875,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.standardnotes.standardnotes;
PRODUCT_NAME = StandardNotes;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "StandardNotes-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -687,6 +887,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 948EE90E15EA48C27577820B /* Pods-StandardNotes.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = Blue;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = StandardNotes/StandardNotes.entitlements;
@@ -710,6 +911,7 @@
PRODUCT_NAME = StandardNotes;
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.standardnotes.standardnotes";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.standardnotes.standardnotes";
SWIFT_OBJC_BRIDGING_HEADER = "StandardNotes-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
@@ -916,6 +1118,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
07CAB7692A618128008FE1EF /* Build configuration list for PBXNativeTarget "Share To SN" */ = {
isa = XCConfigurationList;
buildConfigurations = (
07CAB7672A618128008FE1EF /* Debug */,
07CAB7682A618128008FE1EF /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "StandardNotes" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -1,28 +1,36 @@
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>
#import <WebKit/WKWebsiteDataStore.h>
#import <TrustKit/TrustKit.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[self configurePinning];
[self disableUrlCache];
[self clearWebEditorCache];
NSString *CFBundleIdentifier = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
NSDictionary * initialProperties = @{@"env" : [CFBundleIdentifier isEqualToString:@"com.standardnotes.standardnotes.dev"] ? @"dev" : @"prod"};
self.moduleName = @"StandardNotes";
self.initialProps = @{};
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@@ -50,12 +58,12 @@
if(![currentVersion isEqualToString:lastVersionClear]) {
// UIWebView
[[NSURLCache sharedURLCache] removeAllCachedResponses];
// WebKit
NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{}];
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:lastVersionClearKey];
}
}
@@ -65,17 +73,17 @@
NSDictionary *trustKitConfig =
@{
kTSKSwizzleNetworkDelegates: @YES,
// The list of domains we want to pin and their configuration
kTSKPinnedDomains: @{
@"standardnotes.org" : @{
kTSKIncludeSubdomains:@YES,
kTSKEnforcePinning:@YES,
// Send reports for pin validation failures so we can track them
kTSKReportUris: @[@"https://standard.report-uri.com/r/d/hpkp/reportOnly"],
// The pinned public keys' Subject Public Key Info hashes
kTSKPublicKeyHashes : @[
@"Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=",
@@ -90,12 +98,12 @@
},
@"standardnotes.com" : @{
kTSKIncludeSubdomains:@YES,
kTSKEnforcePinning:@YES,
// Send reports for pin validation failures so we can track them
kTSKReportUris: @[@"https://standard.report-uri.com/r/d/hpkp/reportOnly"],
// The pinned public keys' Subject Public Key Info hashes
kTSKPublicKeyHashes : @[
@"Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=",
@@ -110,7 +118,7 @@
},
}
};
[TrustKit initSharedInstanceWithConfiguration:trustKitConfig];
}
}

View File

@@ -46,6 +46,18 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
<dict/>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>http</string>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:app.standardnotes.com</string>
</array>
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.standardnotes.standardnotes</string>
</array>
</dict>
</plist>

View File

@@ -1,6 +1,6 @@
import { ReactNativeToWebEvent } from '@standardnotes/snjs'
import { RefObject } from 'react'
import { AppState, NativeEventSubscription, NativeModules, Platform } from 'react-native'
import { AppState, Linking, NativeEventSubscription, NativeModules, Platform } from 'react-native'
import { readFile } from 'react-native-fs'
import WebView from 'react-native-webview'
const { ReceiveSharingIntent } = NativeModules
@@ -13,13 +13,19 @@ type ReceivedItem = {
text?: string | null
weblink?: string | null
subject?: string | null
path?: string | null
}
type ReceivedFile = ReceivedItem & {
type ReceivedAndroidFile = ReceivedItem & {
contentUri: string
mimeType: string
}
type ReceivedIosFile = ReceivedItem & {
path: string
mimeType: string
}
type ReceivedWeblink = ReceivedItem & {
weblink: string
}
@@ -28,10 +34,14 @@ type ReceivedText = ReceivedItem & {
text: string
}
const isReceivedFile = (item: ReceivedItem): item is ReceivedFile => {
const isReceivedAndroidFile = (item: ReceivedItem): item is ReceivedAndroidFile => {
return !!item.contentUri && !!item.mimeType
}
const isReceivedIosFile = (item: ReceivedItem): item is ReceivedIosFile => {
return !!item.path
}
const isReceivedWeblink = (item: ReceivedItem): item is ReceivedWeblink => {
return !!item.weblink
}
@@ -40,15 +50,16 @@ const isReceivedText = (item: ReceivedItem): item is ReceivedText => {
return !!item.text
}
const BundleIdentifier = 'com.standardnotes.standardnotes'
const IosUrlToCheckFor = `${BundleIdentifier}://dataUrl`
export class ReceivedSharedItemsHandler {
private appStateEventSub: NativeEventSubscription | null = null
private eventSub: NativeEventSubscription | null = null
private receivedItemsQueue: ReceivedItem[] = []
private isApplicationLaunched = false
constructor(private webViewRef: RefObject<WebView>) {
if (Platform.OS === 'android') {
this.registerNativeEventSub()
}
this.registerNativeEventSub()
}
setIsApplicationLaunched = (isApplicationLaunched: boolean) => {
@@ -61,23 +72,29 @@ export class ReceivedSharedItemsHandler {
deinit() {
this.receivedItemsQueue = []
this.appStateEventSub?.remove()
this.eventSub?.remove()
}
private registerNativeEventSub = () => {
this.appStateEventSub = AppState.addEventListener('change', (state) => {
if (state === 'active') {
ReceiveSharingIntent.getFileNames()
.then(async (filesObject: Record<string, ReceivedItem>) => {
const items = Object.values(filesObject)
this.receivedItemsQueue.push(...items)
if (Platform.OS === 'ios') {
Linking.getInitialURL()
.then((url) => {
if (url && url.startsWith(IosUrlToCheckFor)) {
this.addSharedItemsToQueue(url)
}
})
.catch(console.error)
this.eventSub = Linking.addEventListener('url', ({ url }) => {
if (url && url.startsWith(IosUrlToCheckFor)) {
this.addSharedItemsToQueue(url)
}
})
return
}
if (this.isApplicationLaunched) {
this.handleItemsQueue().catch(console.error)
}
})
.then(() => ReceiveSharingIntent.clearFileNames())
.catch(console.error)
this.eventSub = AppState.addEventListener('change', (state) => {
if (state === 'active') {
this.addSharedItemsToQueue()
}
})
}
@@ -92,7 +109,7 @@ export class ReceivedSharedItemsHandler {
return
}
if (isReceivedFile(item)) {
if (isReceivedAndroidFile(item)) {
const data = await readFile(item.contentUri, 'base64')
const file = {
name: item.fileName || item.contentUri,
@@ -106,6 +123,20 @@ export class ReceivedSharedItemsHandler {
messageData: file,
}),
)
} else if (isReceivedIosFile(item)) {
const data = await readFile(item.path, 'base64')
const file = {
name: item.fileName || item.path,
data,
mimeType: item.mimeType,
}
this.webViewRef.current?.postMessage(
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.ReceivedFile,
messageType: 'event',
messageData: file,
}),
)
} else if (isReceivedWeblink(item)) {
this.webViewRef.current?.postMessage(
JSON.stringify({
@@ -131,4 +162,49 @@ export class ReceivedSharedItemsHandler {
this.handleItemsQueue().catch(console.error)
}
private addSharedItemsToQueue = (url?: string) => {
ReceiveSharingIntent.getFileNames(url)
.then(async (received: unknown) => {
if (!received) {
return
}
if (Platform.OS === 'android') {
const items = Object.values(received as Record<string, ReceivedItem>)
this.receivedItemsQueue.push(...items)
} else if (typeof received === 'string') {
const parsed: unknown = JSON.parse(received)
if (typeof parsed !== 'object') {
return
}
if (!parsed) {
return
}
if ('media' in parsed && Array.isArray(parsed.media)) {
this.receivedItemsQueue.push(...parsed.media)
}
if ('text' in parsed && Array.isArray(parsed.text)) {
this.receivedItemsQueue.push(
...parsed.text.map((text: string) => ({
text: text,
})),
)
}
if ('urls' in parsed && Array.isArray(parsed.urls)) {
this.receivedItemsQueue.push(
...parsed.urls.map((url: string) => ({
weblink: url,
})),
)
}
}
if (this.isApplicationLaunched) {
this.handleItemsQueue().catch(console.error)
}
})
.then(() => ReceiveSharingIntent.clearFileNames())
.catch(console.error)
}
}