From 8f0eccd7b62b380cdff045ef53fe40f087e50e9d Mon Sep 17 00:00:00 2001 From: Grishka Date: Tue, 26 Sep 2023 03:22:23 +0300 Subject: [PATCH] An experimental share extension to send files and too much refactoring closes #4 --- NearDrop.xcodeproj/project.pbxproj | 441 ++++++++++++++++-- .../xcschemes/ShareExtension.xcscheme | 95 ++++ NearDrop/AppDelegate.swift | 73 ++- NearDrop/NearbyConnectionManager.swift | 134 ------ NearDrop/es.lproj/Localizable.strings | Bin 2600 -> 2602 bytes .../Data+Extensions.swift | 16 + {NearDrop => NearbyShare}/GenerateProtobuf.sh | 0 .../InboundNearbyConnection.swift | 6 +- .../NearbyConnection.swift | 76 +-- NearbyShare/NearbyConnectionManager.swift | 374 +++++++++++++++ NearbyShare/OutboundNearbyConnection.swift | 424 +++++++++++++++++ .../device_to_device_messages.pb.swift | 0 .../Protobuf/offline_wire_formats.pb.swift | 0 .../Protobuf/securegcm.pb.swift | 0 .../Protobuf/securemessage.pb.swift | 0 .../Protobuf/ukey.pb.swift | 0 .../Protobuf/wire_format.pb.swift | 0 .../device_to_device_messages.proto | 0 .../ProtobufSource/offline_wire_formats.proto | 0 .../ProtobufSource/securegcm.proto | 0 .../ProtobufSource/securemessage.proto | 0 .../ProtobufSource/ukey.proto | 0 .../ProtobufSource/wire_format.proto | 0 PROTOCOL.md | 3 +- ShareExtension/Assets.xcassets/Contents.json | 6 + .../NearDropIcon.imageset/16.png | Bin 0 -> 1005 bytes .../NearDropIcon.imageset/32.png | Bin 0 -> 2080 bytes .../NearDropIcon.imageset/Contents.json | 22 + ShareExtension/Base.lproj/Localizable.strings | Bin 0 -> 1488 bytes .../Base.lproj/Localizable.stringsdict | 22 + .../Base.lproj/ShareViewController.xib | 264 +++++++++++ ShareExtension/DeviceListCell.swift | 26 ++ ShareExtension/DeviceListCell.xib | 61 +++ ShareExtension/Info.plist | 27 ++ ShareExtension/ShareExtension.entitlements | 14 + ShareExtension/ShareViewController.swift | 271 +++++++++++ ShareExtension/ru.lproj/Localizable.strings | Bin 0 -> 1532 bytes .../ru.lproj/Localizable.stringsdict | 26 ++ .../ru.lproj/ShareViewController.strings | 12 + 39 files changed, 2158 insertions(+), 235 deletions(-) create mode 100644 NearDrop.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme delete mode 100644 NearDrop/NearbyConnectionManager.swift rename {NearDrop => NearbyShare}/Data+Extensions.swift (64%) rename {NearDrop => NearbyShare}/GenerateProtobuf.sh (100%) rename {NearDrop => NearbyShare}/InboundNearbyConnection.swift (99%) rename {NearDrop => NearbyShare}/NearbyConnection.swift (93%) create mode 100644 NearbyShare/NearbyConnectionManager.swift create mode 100644 NearbyShare/OutboundNearbyConnection.swift rename {NearDrop => NearbyShare}/Protobuf/device_to_device_messages.pb.swift (100%) rename {NearDrop => NearbyShare}/Protobuf/offline_wire_formats.pb.swift (100%) rename {NearDrop => NearbyShare}/Protobuf/securegcm.pb.swift (100%) rename {NearDrop => NearbyShare}/Protobuf/securemessage.pb.swift (100%) rename {NearDrop => NearbyShare}/Protobuf/ukey.pb.swift (100%) rename {NearDrop => NearbyShare}/Protobuf/wire_format.pb.swift (100%) rename {NearDrop => NearbyShare}/ProtobufSource/device_to_device_messages.proto (100%) rename {NearDrop => NearbyShare}/ProtobufSource/offline_wire_formats.proto (100%) rename {NearDrop => NearbyShare}/ProtobufSource/securegcm.proto (100%) rename {NearDrop => NearbyShare}/ProtobufSource/securemessage.proto (100%) rename {NearDrop => NearbyShare}/ProtobufSource/ukey.proto (100%) rename {NearDrop => NearbyShare}/ProtobufSource/wire_format.proto (100%) create mode 100644 ShareExtension/Assets.xcassets/Contents.json create mode 100644 ShareExtension/Assets.xcassets/NearDropIcon.imageset/16.png create mode 100644 ShareExtension/Assets.xcassets/NearDropIcon.imageset/32.png create mode 100644 ShareExtension/Assets.xcassets/NearDropIcon.imageset/Contents.json create mode 100644 ShareExtension/Base.lproj/Localizable.strings create mode 100644 ShareExtension/Base.lproj/Localizable.stringsdict create mode 100644 ShareExtension/Base.lproj/ShareViewController.xib create mode 100644 ShareExtension/DeviceListCell.swift create mode 100644 ShareExtension/DeviceListCell.xib create mode 100644 ShareExtension/Info.plist create mode 100644 ShareExtension/ShareExtension.entitlements create mode 100644 ShareExtension/ShareViewController.swift create mode 100644 ShareExtension/ru.lproj/Localizable.strings create mode 100644 ShareExtension/ru.lproj/Localizable.stringsdict create mode 100644 ShareExtension/ru.lproj/ShareViewController.strings diff --git a/NearDrop.xcodeproj/project.pbxproj b/NearDrop.xcodeproj/project.pbxproj index 9652010..37a4a00 100644 --- a/NearDrop.xcodeproj/project.pbxproj +++ b/NearDrop.xcodeproj/project.pbxproj @@ -7,26 +7,63 @@ objects = { /* Begin PBXBuildFile section */ - 698DFAE629E2F91A0064F247 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; }; + 691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53B92ABB70840089FD92 /* DeviceListCell.swift */; }; + 691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 691F53BA2ABB70840089FD92 /* DeviceListCell.xib */; }; + 691F53BE2ABF03820089FD92 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; }; + 691F53C72AC2594E0089FD92 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 691F53C52AC2594E0089FD92 /* Localizable.strings */; }; + 691F53CB2AC2599B0089FD92 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 691F53C92AC2599B0089FD92 /* Localizable.stringsdict */; }; 698DFB0329E362140064F247 /* NDNotificationCenterHackery.m in Sources */ = {isa = PBXBuildFile; fileRef = 698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */; }; + 699B03452AB5FBA300E0D718 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 699B03442AB5FBA300E0D718 /* Assets.xcassets */; }; + 699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699DEBA52AB0573200115D22 /* ShareViewController.swift */; }; + 699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 699DEBA72AB0573200115D22 /* ShareViewController.xib */; }; + 699DEBAE2AB0573200115D22 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 699DEBA12AB0573200115D22 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 69D2C32D29E77F2200EC7E30 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32B29E77F2200EC7E30 /* Localizable.strings */; }; 69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */; }; 69D2C33829E78DF400EC7E30 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C33629E78DF400EC7E30 /* Localizable.stringsdict */; }; 69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; }; 69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; - 69DA9A1F29E0C0B300A442DA /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; }; - 69DA9A2129E0CC4E00A442DA /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */; }; - 69DA9A2329E17F0400A442DA /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; }; - 69DA9A2629E189EF00A442DA /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 69DA9A2529E189EF00A442DA /* SwiftProtobuf */; }; - 69DA9A2E29E18CB500A442DA /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; }; - 69DA9A2F29E18CB500A442DA /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; }; - 69DA9A3029E18CB500A442DA /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; }; - 69DA9A3129E18CB500A442DA /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; }; - 69DA9A3229E18CB500A442DA /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; }; - 69DA9A3329E18CB500A442DA /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; }; - 69DA9A3629E1994C00A442DA /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = 69DA9A3529E1994C00A442DA /* SwiftECC */; }; + 69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; }; + 69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; }; + 69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; }; + 69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; }; + 69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; }; + 69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; }; + 69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; }; + 69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; }; + 69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; }; + 69DCF4932AB70E9700CBE2CC /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */; }; + 69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; }; + 69DCF4952AB8BF7B00CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */; }; + 69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = 69DCF49B2AB8C58500CBE2CC /* SwiftECC */; }; + 69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; }; + 69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 699DEBA02AB0573200115D22; + remoteInfo = ShareExtension; + }; + 69DCF4962AB8BF7B00CBE2CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 69DCF4802AB70D0600CBE2CC; + remoteInfo = NearbyShare; + }; + 69DCF49F2AB8C9A500CBE2CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 69DCF4802AB70D0600CBE2CC; + remoteInfo = NearbyShare; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 698DFAFF29E353220064F247 /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; @@ -34,10 +71,43 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 699DEBAE2AB0573200115D22 /* ShareExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + 699DEBDB2AB2828400115D22 /* Embed XPC Services */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; + dstSubfolderSpec = 16; + files = ( + ); + name = "Embed XPC Services"; + runOnlyForDeploymentPostprocessing = 0; + }; + 69DCF4982AB8BF7B00CBE2CC /* Embed Libraries */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 69DCF4952AB8BF7B00CBE2CC /* libNearbyShare.dylib in Embed Libraries */, + ); + name = "Embed Libraries"; + runOnlyForDeploymentPostprocessing = 0; + }; + 69DCF4A12AB8C9A500CBE2CC /* Embed Libraries */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */, + ); + name = "Embed Libraries"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -45,12 +115,26 @@ 3226184E2A51E10600B06FD1 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 322618502A51EB8A00B06FD1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = ""; }; 322618512A51EB8A00B06FD1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 691F53B92ABB70840089FD92 /* DeviceListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListCell.swift; sourceTree = ""; }; + 691F53BA2ABB70840089FD92 /* DeviceListCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeviceListCell.xib; sourceTree = ""; }; + 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundNearbyConnection.swift; sourceTree = ""; }; + 691F53C42AC257A30089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/ShareViewController.strings; sourceTree = ""; }; + 691F53C62AC2594E0089FD92 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; + 691F53C82AC259630089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 691F53CA2AC2599B0089FD92 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = Base; path = Base.lproj/Localizable.stringsdict; sourceTree = ""; }; + 691F53CC2AC259A20089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyConnection.swift; sourceTree = ""; }; 698DFAED29E353220064F247 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; 698DFAEF29E353220064F247 /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; }; 698DFB0029E362140064F247 /* NearDrop-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NearDrop-Bridging-Header.h"; sourceTree = ""; }; 698DFB0129E362140064F247 /* NDNotificationCenterHackery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NDNotificationCenterHackery.h; sourceTree = ""; }; 698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NDNotificationCenterHackery.m; sourceTree = ""; }; + 699B03442AB5FBA300E0D718 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 699DEBA12AB0573200115D22 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 699DEBA52AB0573200115D22 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; + 699DEBA82AB0573200115D22 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ShareViewController.xib; sourceTree = ""; }; + 699DEBAA2AB0573200115D22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 699DEBAB2AB0573200115D22 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = ""; }; 69D2C32C29E77F2200EC7E30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 69D2C33029E789AF00EC7E30 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; @@ -69,6 +153,7 @@ 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = ""; }; 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = ""; }; 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = ""; }; + 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libNearbyShare.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; A3930F0529EAB2D1008F891D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = ""; }; A3930F0629EAB2D1008F891D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; B670906E2A6D234D00DB8273 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -76,12 +161,28 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 699DEB9E2AB0573200115D22 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 69DA9A0B29E0BF5100A442DA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 69DA9A3629E1994C00A442DA /* SwiftECC in Frameworks */, - 69DA9A2629E189EF00A442DA /* SwiftProtobuf in Frameworks */, + 69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 69DCF47F2AB70D0600CBE2CC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */, + 69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -97,10 +198,28 @@ name = Frameworks; sourceTree = ""; }; + 699DEBA22AB0573200115D22 /* ShareExtension */ = { + isa = PBXGroup; + children = ( + 699DEBA52AB0573200115D22 /* ShareViewController.swift */, + 691F53B92ABB70840089FD92 /* DeviceListCell.swift */, + 691F53BA2ABB70840089FD92 /* DeviceListCell.xib */, + 699DEBA72AB0573200115D22 /* ShareViewController.xib */, + 699B03442AB5FBA300E0D718 /* Assets.xcassets */, + 691F53C92AC2599B0089FD92 /* Localizable.stringsdict */, + 691F53C52AC2594E0089FD92 /* Localizable.strings */, + 699DEBAA2AB0573200115D22 /* Info.plist */, + 699DEBAB2AB0573200115D22 /* ShareExtension.entitlements */, + ); + path = ShareExtension; + sourceTree = ""; + }; 69DA9A0529E0BF5100A442DA = { isa = PBXGroup; children = ( 69DA9A1029E0BF5100A442DA /* NearDrop */, + 699DEBA22AB0573200115D22 /* ShareExtension */, + 69DCF4822AB70D0600CBE2CC /* NearbyShare */, 698DFAEC29E353220064F247 /* Frameworks */, 69DA9A0F29E0BF5100A442DA /* Products */, ); @@ -110,6 +229,8 @@ isa = PBXGroup; children = ( 69DA9A0E29E0BF5100A442DA /* NearDrop.app */, + 699DEBA12AB0573200115D22 /* ShareExtension.appex */, + 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */, ); name = Products; sourceTree = ""; @@ -117,17 +238,12 @@ 69DA9A1029E0BF5100A442DA /* NearDrop */ = { isa = PBXGroup; children = ( - 69DA9A2729E18CB500A442DA /* Protobuf */, 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */, 69DA9A1329E0BF5200A442DA /* Assets.xcassets */, - 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */, 69D2C33629E78DF400EC7E30 /* Localizable.stringsdict */, 69D2C32B29E77F2200EC7E30 /* Localizable.strings */, - 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */, 698DFB0129E362140064F247 /* NDNotificationCenterHackery.h */, 698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */, - 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */, - 69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */, 69DA9A1829E0BF5200A442DA /* NearDrop.entitlements */, 698DFB0029E362140064F247 /* NearDrop-Bridging-Header.h */, 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */, @@ -148,9 +264,51 @@ path = Protobuf; sourceTree = ""; }; + 69DCF4822AB70D0600CBE2CC /* NearbyShare */ = { + isa = PBXGroup; + children = ( + 69DA9A2729E18CB500A442DA /* Protobuf */, + 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */, + 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */, + 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */, + 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */, + 69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */, + ); + path = NearbyShare; + sourceTree = ""; + }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + 69DCF47D2AB70D0600CBE2CC /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ + 699DEBA02AB0573200115D22 /* ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 699DEBB12AB0573200115D22 /* Build configuration list for PBXNativeTarget "ShareExtension" */; + buildPhases = ( + 699DEB9D2AB0573200115D22 /* Sources */, + 699DEB9E2AB0573200115D22 /* Frameworks */, + 699DEB9F2AB0573200115D22 /* Resources */, + 69DCF4A12AB8C9A500CBE2CC /* Embed Libraries */, + ); + buildRules = ( + ); + dependencies = ( + 69DCF4A02AB8C9A500CBE2CC /* PBXTargetDependency */, + ); + name = ShareExtension; + productName = ShareExtension; + productReference = 699DEBA12AB0573200115D22 /* ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 69DA9A0D29E0BF5100A442DA /* NearDrop */ = { isa = PBXNativeTarget; buildConfigurationList = 69DA9A1B29E0BF5200A442DA /* Build configuration list for PBXNativeTarget "NearDrop" */; @@ -159,19 +317,42 @@ 69DA9A0B29E0BF5100A442DA /* Frameworks */, 69DA9A0C29E0BF5100A442DA /* Resources */, 698DFAFF29E353220064F247 /* Embed Foundation Extensions */, + 699DEBDB2AB2828400115D22 /* Embed XPC Services */, + 69DCF4982AB8BF7B00CBE2CC /* Embed Libraries */, + ); + buildRules = ( + ); + dependencies = ( + 699DEBAD2AB0573200115D22 /* PBXTargetDependency */, + 69DCF4972AB8BF7B00CBE2CC /* PBXTargetDependency */, + ); + name = NearDrop; + packageProductDependencies = ( + ); + productName = NearDrop; + productReference = 69DA9A0E29E0BF5100A442DA /* NearDrop.app */; + productType = "com.apple.product-type.application"; + }; + 69DCF4802AB70D0600CBE2CC /* NearbyShare */ = { + isa = PBXNativeTarget; + buildConfigurationList = 69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */; + buildPhases = ( + 69DCF47D2AB70D0600CBE2CC /* Headers */, + 69DCF47E2AB70D0600CBE2CC /* Sources */, + 69DCF47F2AB70D0600CBE2CC /* Frameworks */, ); buildRules = ( ); dependencies = ( ); - name = NearDrop; + name = NearbyShare; packageProductDependencies = ( - 69DA9A2529E189EF00A442DA /* SwiftProtobuf */, - 69DA9A3529E1994C00A442DA /* SwiftECC */, + 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */, + 69DCF49B2AB8C58500CBE2CC /* SwiftECC */, ); - productName = NearDrop; - productReference = 69DA9A0E29E0BF5100A442DA /* NearDrop.app */; - productType = "com.apple.product-type.application"; + productName = NearbyShare; + productReference = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; + productType = "com.apple.product-type.library.dynamic"; }; /* End PBXNativeTarget section */ @@ -183,10 +364,16 @@ LastSwiftUpdateCheck = 1430; LastUpgradeCheck = 1430; TargetAttributes = { + 699DEBA02AB0573200115D22 = { + CreatedOnToolsVersion = 14.3.1; + }; 69DA9A0D29E0BF5100A442DA = { CreatedOnToolsVersion = 14.3; LastSwiftMigration = 1430; }; + 69DCF4802AB70D0600CBE2CC = { + CreatedOnToolsVersion = 14.3.1; + }; }; }; buildConfigurationList = 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "NearDrop" */; @@ -212,11 +399,25 @@ projectRoot = ""; targets = ( 69DA9A0D29E0BF5100A442DA /* NearDrop */, + 699DEBA02AB0573200115D22 /* ShareExtension */, + 69DCF4802AB70D0600CBE2CC /* NearbyShare */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 699DEB9F2AB0573200115D22 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 699B03452AB5FBA300E0D718 /* Assets.xcassets in Resources */, + 691F53C72AC2594E0089FD92 /* Localizable.strings in Resources */, + 691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */, + 691F53CB2AC2599B0089FD92 /* Localizable.stringsdict in Resources */, + 699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 69DA9A0C29E0BF5100A442DA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -231,28 +432,90 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 699DEB9D2AB0573200115D22 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */, + 691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 69DA9A0A29E0BF5100A442DA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 69DA9A2E29E18CB500A442DA /* ukey.pb.swift in Sources */, 698DFB0329E362140064F247 /* NDNotificationCenterHackery.m in Sources */, - 69DA9A2329E17F0400A442DA /* InboundNearbyConnection.swift in Sources */, - 69DA9A1F29E0C0B300A442DA /* NearbyConnectionManager.swift in Sources */, - 69DA9A3029E18CB500A442DA /* device_to_device_messages.pb.swift in Sources */, - 69DA9A3129E18CB500A442DA /* offline_wire_formats.pb.swift in Sources */, 69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */, - 69DA9A2F29E18CB500A442DA /* wire_format.pb.swift in Sources */, - 69DA9A3329E18CB500A442DA /* securemessage.pb.swift in Sources */, - 69DA9A2129E0CC4E00A442DA /* Data+Extensions.swift in Sources */, - 69DA9A3229E18CB500A442DA /* securegcm.pb.swift in Sources */, - 698DFAE629E2F91A0064F247 /* NearbyConnection.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 69DCF47E2AB70D0600CBE2CC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */, + 69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */, + 69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */, + 69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */, + 69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */, + 691F53BE2ABF03820089FD92 /* OutboundNearbyConnection.swift in Sources */, + 69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */, + 69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */, + 69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */, + 69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */, + 69DCF4932AB70E9700CBE2CC /* Data+Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 699DEBAD2AB0573200115D22 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 699DEBA02AB0573200115D22 /* ShareExtension */; + targetProxy = 699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */; + }; + 69DCF4972AB8BF7B00CBE2CC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 69DCF4802AB70D0600CBE2CC /* NearbyShare */; + targetProxy = 69DCF4962AB8BF7B00CBE2CC /* PBXContainerItemProxy */; + }; + 69DCF4A02AB8C9A500CBE2CC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 69DCF4802AB70D0600CBE2CC /* NearbyShare */; + targetProxy = 69DCF49F2AB8C9A500CBE2CC /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ + 691F53C52AC2594E0089FD92 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 691F53C62AC2594E0089FD92 /* Base */, + 691F53C82AC259630089FD92 /* ru */, + ); + name = Localizable.strings; + sourceTree = ""; + }; + 691F53C92AC2599B0089FD92 /* Localizable.stringsdict */ = { + isa = PBXVariantGroup; + children = ( + 691F53CA2AC2599B0089FD92 /* Base */, + 691F53CC2AC259A20089FD92 /* ru */, + ); + name = Localizable.stringsdict; + sourceTree = ""; + }; + 699DEBA72AB0573200115D22 /* ShareViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + 699DEBA82AB0573200115D22 /* Base */, + 691F53C42AC257A30089FD92 /* ru */, + ); + name = ShareViewController.xib; + sourceTree = ""; + }; 69D2C32B29E77F2200EC7E30 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -282,6 +545,58 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 699DEBAF2AB0573200115D22 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NearDrop; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = me.grishka.NearDrop.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 699DEBB02AB0573200115D22 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NearDrop; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = me.grishka.NearDrop.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; 69DA9A1929E0BF5200A442DA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -333,7 +648,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.3; + MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -388,7 +703,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.3; + MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -400,6 +715,7 @@ 69DA9A1C29E0BF5200A442DA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -431,6 +747,7 @@ 69DA9A1D29E0BF5200A442DA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -458,9 +775,46 @@ }; name = Release; }; + 69DCF4872AB70D0600CBE2CC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + EXECUTABLE_PREFIX = lib; + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 69DCF4882AB70D0600CBE2CC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + EXECUTABLE_PREFIX = lib; + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 699DEBB12AB0573200115D22 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 699DEBAF2AB0573200115D22 /* Debug */, + 699DEBB02AB0573200115D22 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "NearDrop" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -479,6 +833,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 69DCF4872AB70D0600CBE2CC /* Debug */, + 69DCF4882AB70D0600CBE2CC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -501,12 +864,12 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 69DA9A2529E189EF00A442DA /* SwiftProtobuf */ = { + 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */ = { isa = XCSwiftPackageProductDependency; package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */; productName = SwiftProtobuf; }; - 69DA9A3529E1994C00A442DA /* SwiftECC */ = { + 69DCF49B2AB8C58500CBE2CC /* SwiftECC */ = { isa = XCSwiftPackageProductDependency; package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */; productName = SwiftECC; diff --git a/NearDrop.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme b/NearDrop.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme new file mode 100644 index 0000000..45a3810 --- /dev/null +++ b/NearDrop.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NearDrop/AppDelegate.swift b/NearDrop/AppDelegate.swift index 7e87a8e..1c78adf 100644 --- a/NearDrop/AppDelegate.swift +++ b/NearDrop/AppDelegate.swift @@ -7,12 +7,12 @@ import Cocoa import UserNotifications +import NearbyShare @main -class AppDelegate: NSObject, NSApplicationDelegate{ - - private var connectionManager:NearbyConnectionManager? +class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, MainAppDelegate{ private var statusItem:NSStatusItem? + private var activeIncomingTransfers:[String:TransferInfo]=[:] func applicationDidFinishLaunching(_ aNotification: Notification) { let menu=NSMenu() @@ -32,11 +32,13 @@ class AppDelegate: NSObject, NSApplicationDelegate{ } } } + nc.delegate=self let incomingTransfersCategory=NDNotificationCenterHackery.hackedNotificationCategory() let errorsCategory=UNNotificationCategory(identifier: "ERRORS", actions: [], intentIdentifiers: []) nc.setNotificationCategories([incomingTransfersCategory, errorsCategory]) - connectionManager=NearbyConnectionManager() - } + NearbyConnectionManager.shared.mainAppDelegate=self + NearbyConnectionManager.shared.becomeVisible() + } func applicationWillTerminate(_ aNotification: Notification) { UNUserNotificationCenter.current().removeAllDeliveredNotifications() @@ -60,5 +62,66 @@ class AppDelegate: NSObject, NSApplicationDelegate{ NSApplication.shared.terminate(nil) } } + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let transferID=response.notification.request.content.userInfo["transferID"]! as! String + NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: response.actionIdentifier=="ACCEPT") + if response.actionIdentifier != "ACCEPT"{ + activeIncomingTransfers.removeValue(forKey: transferID) + } + completionHandler() + } + + func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) { + let notificationContent=UNMutableNotificationContent() + notificationContent.title="NearDrop" + notificationContent.subtitle=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [transfer.pinCode!]) + let fileStr:String + if transfer.files.count==1{ + fileStr=transfer.files[0].name + }else{ + fileStr=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), transfer.files.count) + } + notificationContent.body=String(format: NSLocalizedString("DeviceSendingFiles", value: "%1$@ is sending you %2$@", comment: ""), arguments: [device.name, fileStr]) + notificationContent.sound = .default + notificationContent.categoryIdentifier="INCOMING_TRANSFERS" + notificationContent.userInfo=["transferID": transfer.id] + NDNotificationCenterHackery.removeDefaultAction(notificationContent) + let notificationReq=UNNotificationRequest(identifier: "transfer_"+transfer.id, content: notificationContent, trigger: nil) + self.activeIncomingTransfers[transfer.id]=TransferInfo(device: device, transfer: transfer) + UNUserNotificationCenter.current().add(notificationReq) + } + + func incomingTransfer(id: String, didFinishWith error: Error?) { + guard let transfer=self.activeIncomingTransfers[id] else {return} + if let error=error{ + let notificationContent=UNMutableNotificationContent() + notificationContent.title=String(format: NSLocalizedString("TransferError", value: "Failed to receive files from %@", comment: ""), arguments: [transfer.device.name]) + if let ne=(error as? NearbyError){ + switch ne{ + case .inputOutput(let er): + notificationContent.body=er.localizedDescription + case .protocolError(_): + notificationContent.body=NSLocalizedString("Error.Protocol", value: "Communication error", comment: "") + case .requiredFieldMissing: + notificationContent.body=NSLocalizedString("Error.Protocol", value: "Communication error", comment: "") + case .ukey2: + notificationContent.body=NSLocalizedString("Error.Crypto", value: "Encryption error", comment: "") + case .canceled(reason: _): + break; // can't happen for incoming transfers + } + }else{ + notificationContent.body=error.localizedDescription + } + notificationContent.categoryIdentifier="ERRORS" + UNUserNotificationCenter.current().add(UNNotificationRequest(identifier: "transferError_"+id, content: notificationContent, trigger: nil)) + } + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["transfer_"+id]) + self.activeIncomingTransfers.removeValue(forKey: id) + } } +struct TransferInfo{ + let device:RemoteDeviceInfo + let transfer:TransferMetadata +} diff --git a/NearDrop/NearbyConnectionManager.swift b/NearDrop/NearbyConnectionManager.swift deleted file mode 100644 index aedd0d3..0000000 --- a/NearDrop/NearbyConnectionManager.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// NearbyConnectionManager.swift -// NearDrop -// -// Created by Grishka on 08.04.2023. -// - -import Foundation -import Network -import UserNotifications - -class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, UNUserNotificationCenterDelegate{ - - private var tcpListener:NWListener; - private let endpointID:[UInt8]=generateEndpointID() - private var mdnsService:NetService? - private var activeConnections:[String:InboundNearbyConnection]=[:] - - override init() { - tcpListener=try! NWListener(using: NWParameters(tls: .none)) - super.init() - UNUserNotificationCenter.current().delegate=self - startTCPListener() - } - - private func startTCPListener(){ - tcpListener.stateUpdateHandler={(state:NWListener.State) in - if case .ready = state { - self.initMDNS() - } - } - tcpListener.newConnectionHandler={(connection:NWConnection) in - let id=UUID().uuidString - let conn=InboundNearbyConnection(connection: connection, id: id) - self.activeConnections[id]=conn - conn.delegate=self - conn.start() - } - tcpListener.start(queue: .global(qos: .utility)) - } - - private static func generateEndpointID()->[UInt8]{ - var id:[UInt8]=[] - let alphabet="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".compactMap {UInt8($0.asciiValue!)} - for _ in 0...3{ - id.append(alphabet[Int.random(in: 0.. Void) { - activeConnections[response.notification.request.content.userInfo["transferID"]! as! String]?.submitUserConsent(accepted: response.actionIdentifier=="ACCEPT") - completionHandler() - } -} - diff --git a/NearDrop/es.lproj/Localizable.strings b/NearDrop/es.lproj/Localizable.strings index ce6625c819ab26ff53e9e5e3955560d50904583f..499423ed2cda090ffa1c76257dad21a7866149ff 100644 GIT binary patch delta 15 WcmZ1>vPxt_3Fl-zj=0GeI3oZq00ol( delta 15 WcmZ1_vO;7-3Fl-3j+n`pI3oZq4h59} diff --git a/NearDrop/Data+Extensions.swift b/NearbyShare/Data+Extensions.swift similarity index 64% rename from NearDrop/Data+Extensions.swift rename to NearbyShare/Data+Extensions.swift index 94e3e18..efa8472 100644 --- a/NearDrop/Data+Extensions.swift +++ b/NearbyShare/Data+Extensions.swift @@ -28,4 +28,20 @@ extension Data{ } return data } + + static func dataFromUrlSafeBase64(_ str:String)->Data?{ + var regularB64=String(str.map{ + if $0=="_"{ + return "/" + }else if $0=="-"{ + return "+" + }else{ + return $0 + } + }) + while (regularB64.count%4) != 0{ + regularB64=regularB64+"=" + } + return Data(base64Encoded: regularB64, options: .ignoreUnknownCharacters) + } } diff --git a/NearDrop/GenerateProtobuf.sh b/NearbyShare/GenerateProtobuf.sh similarity index 100% rename from NearDrop/GenerateProtobuf.sh rename to NearbyShare/GenerateProtobuf.sh diff --git a/NearDrop/InboundNearbyConnection.swift b/NearbyShare/InboundNearbyConnection.swift similarity index 99% rename from NearDrop/InboundNearbyConnection.swift rename to NearbyShare/InboundNearbyConnection.swift index 5314b40..f48025c 100644 --- a/NearDrop/InboundNearbyConnection.swift +++ b/NearbyShare/InboundNearbyConnection.swift @@ -63,8 +63,10 @@ class InboundNearbyConnection: NearbyConnection{ } }catch{ lastError=error - print("Deserialization error: \(error)") + print("Deserialization error: \(error) in state \(currentState)") +#if !DEBUG protocolError() +#endif } } @@ -279,7 +281,7 @@ class InboundNearbyConnection: NearbyConnection{ destinationURL: dest) transferredFiles[file.payloadID]=info } - let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta})) + let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode) DispatchQueue.main.async { self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) } diff --git a/NearDrop/NearbyConnection.swift b/NearbyShare/NearbyConnection.swift similarity index 93% rename from NearDrop/NearbyConnection.swift rename to NearbyShare/NearbyConnection.swift index 2892b6e..a9ca441 100644 --- a/NearDrop/NearbyConnection.swift +++ b/NearbyShare/NearbyConnection.swift @@ -16,6 +16,7 @@ import BigInt class NearbyConnection{ internal static let SANE_FRAME_LENGTH=5*1024*1024 + private static let dispatchQueue=DispatchQueue(label: "me.grishka.NearDrop.queue", qos: .utility) // FIFO (non-concurrent) queue to avoid those exciting concurrency bugs internal let connection:NWConnection internal var remoteDeviceInfo:RemoteDeviceInfo? @@ -52,6 +53,7 @@ class NearbyConnection{ func start(){ connection.stateUpdateHandler={state in if case .ready = state { + self.connectionReady() self.receiveFrameAsync() } else if case .failed(let err) = state { self.lastError=err @@ -59,9 +61,12 @@ class NearbyConnection{ self.handleConnectionClosure() } } - connection.start(queue: .global(qos: .utility)) + //connection.start(queue: .global(qos: .utility)) + connection.start(queue: NearbyConnection.dispatchQueue) } + func connectionReady(){} + internal func handleConnectionClosure(){ print("Connection closed") } @@ -132,22 +137,27 @@ class NearbyConnection{ } } - internal func sendFrameAsync(_ frame:Data){ + internal func sendFrameAsync(_ frame:Data, completion:(()->Void)?=nil){ + if connectionClosed{ + return + } var lengthPrefixedData=Data(capacity: frame.count+4) let length:Int=frame.count lengthPrefixedData.append(contentsOf: [ - UInt8(length >> 24), - UInt8(length >> 16), - UInt8(length >> 8), - UInt8(length) + UInt8(truncatingIfNeeded: length >> 24), + UInt8(truncatingIfNeeded: length >> 16), + UInt8(truncatingIfNeeded: length >> 8), + UInt8(truncatingIfNeeded: length) ]) lengthPrefixedData.append(frame) connection.send(content: lengthPrefixedData, completion: .contentProcessed({ error in - + if let completion=completion{ + completion() + } })) } - internal func encryptAndSendOfflineFrame(_ frame:Location_Nearby_Connections_OfflineFrame) throws{ + internal func encryptAndSendOfflineFrame(_ frame:Location_Nearby_Connections_OfflineFrame, completion:(()->Void)?=nil) throws{ var d2dMsg=Securegcm_DeviceToDeviceMessage() serverSeq+=1 d2dMsg.sequenceNumber=serverSeq @@ -185,7 +195,7 @@ class NearbyConnection{ var smsg=Securemessage_SecureMessage() smsg.headerAndBody=try hb.serializedData() smsg.signature=Data(HMAC.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!)) - sendFrameAsync(try smsg.serializedData()) + sendFrameAsync(try smsg.serializedData(), completion: completion) } internal func sendTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{ @@ -275,6 +285,9 @@ class NearbyConnection{ try processFileChunk(frame: payloadTransfer) } }else if case .keepAlive = offlineFrame.v1.type{ + #if DEBUG + print("Sent keep-alive") + #endif sendKeepAlive(ack: true) }else{ print("Unhandled offline frame encrypted: \(offlineFrame)") @@ -402,51 +415,6 @@ class NearbyConnection{ } } -enum NearbyError:Error{ - case protocolError(_ message:String) - case requiredFieldMissing - case ukey2 - case inputOutput(cause:Errno) -} - - -struct RemoteDeviceInfo{ - let name:String - let type:DeviceType - - enum DeviceType{ - case unknown - case phone - case tablet - case computer - - static func fromRawValue(value:Int) -> DeviceType{ - switch value { - case 0: - return .unknown - case 1: - return .phone - case 2: - return .tablet - case 3: - return .computer - default: - return .unknown - } - } - } -} - -struct TransferMetadata{ - let files:[FileMetadata] -} - -struct FileMetadata{ - let name:String - let size:Int64 - let mimeType:String -} - struct InternalFileInfo{ let meta:FileMetadata let payloadID:Int64 diff --git a/NearbyShare/NearbyConnectionManager.swift b/NearbyShare/NearbyConnectionManager.swift new file mode 100644 index 0000000..3307bd8 --- /dev/null +++ b/NearbyShare/NearbyConnectionManager.swift @@ -0,0 +1,374 @@ +// +// NearbyConnectionManager.swift +// NearDrop +// +// Created by Grishka on 08.04.2023. +// + +import Foundation +import Network +import System + +public struct RemoteDeviceInfo{ + public let name:String + public let type:DeviceType + public var id:String? + + init(name: String, type: DeviceType, id: String? = nil) { + self.name = name + self.type = type + self.id = id + } + + init(info:EndpointInfo){ + self.name=info.name + self.type=info.deviceType + } + + public enum DeviceType:Int32{ + case unknown=0 + case phone + case tablet + case computer + + public static func fromRawValue(value:Int) -> DeviceType{ + switch value { + case 0: + return .unknown + case 1: + return .phone + case 2: + return .tablet + case 3: + return .computer + default: + return .unknown + } + } + } +} + + +public enum NearbyError:Error{ + case protocolError(_ message:String) + case requiredFieldMissing + case ukey2 + case inputOutput(cause:Errno) + case canceled(reason:CancellationReason) + + public enum CancellationReason{ + case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut + } +} + +public struct TransferMetadata{ + public let files:[FileMetadata] + public let id:String + public let pinCode:String? +} + +public struct FileMetadata{ + public let name:String + public let size:Int64 + public let mimeType:String +} + +struct FoundServiceInfo{ + let service:NWBrowser.Result + var device:RemoteDeviceInfo? +} + +struct OutgoingTransferInfo{ + let service:NWBrowser.Result + let device:RemoteDeviceInfo + let connection:OutboundNearbyConnection + let delegate:ShareExtensionDelegate +} + +struct EndpointInfo{ + let name:String + let deviceType:RemoteDeviceInfo.DeviceType + + init(name: String, deviceType: RemoteDeviceInfo.DeviceType){ + self.name = name + self.deviceType = deviceType + } + + init?(data:Data){ + guard data.count>17 else {return nil} + let deviceNameLength=Int(data[17]) + guard data.count>=deviceNameLength+18 else {return nil} + guard let deviceName=String(data: data[18..<(18+deviceNameLength)], encoding: .utf8) else {return nil} + let rawDeviceType:Int=Int(data[0] & 7) >> 1 + self.name=deviceName + self.deviceType=RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType) + } + + func serialize()->Data{ + // 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits) + // Device types: unknown=0, phone=1, tablet=2, laptop=3 + var endpointInfo:[UInt8]=[UInt8(deviceType.rawValue << 1)] + // 16 bytes: unknown random bytes + for _ in 0...15{ + endpointInfo.append(UInt8.random(in: 0...255)) + } + // Device name in UTF-8 prefixed with 1-byte length + var nameChars=[UInt8](name.utf8) + if nameChars.count>255{ + nameChars=[UInt8](nameChars[0..<255]) + } + endpointInfo.append(UInt8(nameChars.count)) + for ch in nameChars{ + endpointInfo.append(UInt8(ch)) + } + return Data(endpointInfo) + } +} + +public protocol MainAppDelegate{ + func obtainUserConsent(for transfer:TransferMetadata, from device:RemoteDeviceInfo) + func incomingTransfer(id:String, didFinishWith error:Error?) +} + +public protocol ShareExtensionDelegate:AnyObject{ + func addDevice(device:RemoteDeviceInfo) + func removeDevice(id:String) + func connectionWasEstablished(pinCode:String) + func connectionFailed(with error:Error) + func transferAccepted() + func transferProgress(progress:Double) + func transferFinished() +} + +public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate{ + + private var tcpListener:NWListener; + public let endpointID:[UInt8]=generateEndpointID() + private var mdnsService:NetService? + private var activeConnections:[String:InboundNearbyConnection]=[:] + private var foundServices:[String:FoundServiceInfo]=[:] + private var shareExtensionDelegates:[ShareExtensionDelegate]=[] + private var outgoingTransfers:[String:OutgoingTransferInfo]=[:] + public var mainAppDelegate:(any MainAppDelegate)? + private var discoveryRefCount=0 + + private var browser:NWBrowser? + + public static let shared=NearbyConnectionManager() + + override init() { + tcpListener=try! NWListener(using: NWParameters(tls: .none)) + super.init() + } + + public func becomeVisible(){ + startTCPListener() + } + + private func startTCPListener(){ + tcpListener.stateUpdateHandler={(state:NWListener.State) in + if case .ready = state { + self.initMDNS() + } + } + tcpListener.newConnectionHandler={(connection:NWConnection) in + let id=UUID().uuidString + let conn=InboundNearbyConnection(connection: connection, id: id) + self.activeConnections[id]=conn + conn.delegate=self + conn.start() + } + tcpListener.start(queue: .global(qos: .utility)) + } + + private static func generateEndpointID()->[UInt8]{ + var id:[UInt8]=[] + let alphabet="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".compactMap {UInt8($0.asciiValue!)} + for _ in 0...3{ + id.append(alphabet[Int.random(in: 0..=0) + if discoveryRefCount==0{ + browser?.cancel() + browser=nil + } + } + + public func addShareExtensionDelegate(_ delegate:ShareExtensionDelegate){ + shareExtensionDelegates.append(delegate) + for service in foundServices.values{ + guard let device=service.device else {continue} + delegate.addDevice(device: device) + } + } + + public func removeShareExtensionDelegate(_ delegate:ShareExtensionDelegate){ + shareExtensionDelegates.removeAll(where: {$0===delegate}) + } + + public func cancelOutgoingTransfer(id:String){ + guard let transfer=outgoingTransfers[id] else {return} + transfer.connection.cancel() + } + + private func endpointID(for service:NWBrowser.Result)->String?{ + guard case let NWEndpoint.service(name: serviceName, type: _, domain: _, interface: _)=service.endpoint else {return nil} + guard let nameData=Data.dataFromUrlSafeBase64(serviceName) else {return nil} + guard nameData.count>=10 else {return nil} + let pcp=nameData[0] + guard pcp==0x23 else {return nil} + let endpointID=String(data: nameData.subdata(in: 1..<5), encoding: .ascii)! + let serviceIDHash=nameData.subdata(in: 5..<8) + guard serviceIDHash==Data([0xFC, 0x9F, 0x5E]) else {return nil} + return endpointID + } + + private func maybeAddFoundDevice(service:NWBrowser.Result){ + #if DEBUG + print("found service \(service)") + #endif + guard let endpointID=endpointID(for: service) else {return} + #if DEBUG + print("service name is valid, endpoint ID \(endpointID)") + #endif + var foundService=FoundServiceInfo(service: service) + + guard case let NWBrowser.Result.Metadata.bonjour(txtRecord)=service.metadata else {return} + guard let endpointInfoEncoded=txtRecord.dictionary["n"] else {return} + guard let endpointInfo=Data.dataFromUrlSafeBase64(endpointInfoEncoded) else {return} + guard endpointInfo.count>=19 else {return} + let deviceType=RemoteDeviceInfo.DeviceType.fromRawValue(value: (Int(endpointInfo[0]) >> 1) & 7) + let deviceNameLength=Int(endpointInfo[17]) + guard endpointInfo.count>=deviceNameLength+17 else {return} + guard let deviceName=String(data: endpointInfo.subdata(in: 18..<(18+deviceNameLength)), encoding: .utf8) else {return} + + let deviceInfo=RemoteDeviceInfo(name: deviceName, type: deviceType, id: endpointID) + foundService.device=deviceInfo + foundServices[endpointID]=foundService + for delegate in shareExtensionDelegates{ + delegate.addDevice(device: deviceInfo) + } + } + + private func maybeRemoveFoundDevice(service:NWBrowser.Result){ + guard let endpointID=endpointID(for: service) else {return} + guard let _=foundServices.removeValue(forKey: endpointID) else {return} + for delegate in shareExtensionDelegates { + delegate.removeDevice(id: endpointID) + } + } + + public func startOutgoingTransfer(deviceID:String, delegate:ShareExtensionDelegate, urls:[URL]){ + guard let info=foundServices[deviceID] else {return} + let tcp=NWProtocolTCP.Options.init() + tcp.noDelay=true + let nwconn=NWConnection(to: info.service.endpoint, using: NWParameters(tls: .none, tcp: tcp)) + let conn=OutboundNearbyConnection(connection: nwconn, id: deviceID, urlsToSend: urls) + conn.delegate=self + let transfer=OutgoingTransferInfo(service: info.service, device: info.device!, connection: conn, delegate: delegate) + outgoingTransfers[deviceID]=transfer + conn.start() + } + + func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) { + guard let transfer=outgoingTransfers[connection.id] else {return} + DispatchQueue.main.async { + transfer.delegate.connectionWasEstablished(pinCode: connection.pinCode!) + } + } + + func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) { + guard let transfer=outgoingTransfers[connection.id] else {return} + DispatchQueue.main.async { + transfer.delegate.transferAccepted() + } + } + + func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) { + guard let transfer=outgoingTransfers[connection.id] else {return} + DispatchQueue.main.async { + transfer.delegate.transferProgress(progress: transferProgress) + } + } + + func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) { + guard let transfer=outgoingTransfers[connection.id] else {return} + DispatchQueue.main.async { + transfer.delegate.connectionFailed(with: failedWithError) + } + outgoingTransfers.removeValue(forKey: connection.id) + } + + func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) { + guard let transfer=outgoingTransfers[connection.id] else {return} + DispatchQueue.main.async { + transfer.delegate.transferFinished() + } + outgoingTransfers.removeValue(forKey: connection.id) + } +} + diff --git a/NearbyShare/OutboundNearbyConnection.swift b/NearbyShare/OutboundNearbyConnection.swift new file mode 100644 index 0000000..84f544b --- /dev/null +++ b/NearbyShare/OutboundNearbyConnection.swift @@ -0,0 +1,424 @@ +// +// OutboundNearbyConnection.swift +// NearbyShare +// +// Created by Grishka on 23.09.2023. +// + +import Foundation +import Network +import CryptoKit +import CommonCrypto +import System +import UniformTypeIdentifiers + +import SwiftECC +import BigInt + +class OutboundNearbyConnection:NearbyConnection{ + private var currentState:State = .initial + private let urlsToSend:[URL] + private var ukeyClientFinishMsgData:Data? + private var queue:[OutgoingFileTransfer]=[] + private var currentTransfer:OutgoingFileTransfer? + public var delegate:OutboundNearbyConnectionDelegate? + private var totalBytesToSend:Int64=0 + private var totalBytesSent:Int64=0 + private var cancelled:Bool=false + + enum State{ + case initial, sentUkeyClientInit, sentUkeyClientFinish, sentPairedKeyEncryption, sentPairedKeyResult, sentIntroduction, sendingFiles + } + + init(connection: NWConnection, id: String, urlsToSend:[URL]){ + self.urlsToSend=urlsToSend + super.init(connection: connection, id: id) + } + + deinit { + if let transfer=currentTransfer, let handle=transfer.handle{ + try? handle.close() + } + for transfer in queue{ + if let handle=transfer.handle{ + try? handle.close() + } + } + } + + public func cancel(){ + cancelled=true + if encryptionDone{ + var cancel=Sharing_Nearby_Frame() + cancel.version = .v1 + cancel.v1=Sharing_Nearby_V1Frame() + cancel.v1.type = .cancel + try? sendTransferSetupFrame(cancel) + } + try? sendDisconnectionAndDisconnect() + } + + override func connectionReady() { + super.connectionReady() + do{ + try sendConnectionRequest() + try sendUkey2ClientInit() + }catch{ + lastError=error + protocolError() + } + } + + override func isServer() -> Bool { + return false + } + + override func processReceivedFrame(frameData: Data) { + do{ + #if DEBUG + print("received \(frameData), state is \(currentState)") + #endif + switch currentState { + case .initial: + protocolError() + case .sentUkeyClientInit: + try processUkey2ServerInit(frame: try Securegcm_Ukey2Message(serializedData: frameData), raw: frameData) + case .sentUkeyClientFinish: + try processConnectionResponse(frame: try Location_Nearby_Connections_OfflineFrame(serializedData: frameData)) + default: + let smsg=try Securemessage_SecureMessage(serializedData: frameData) + try decryptAndProcessReceivedSecureMessage(smsg) + } + }catch{ + if case NearbyError.ukey2=error{ + }else if currentState == .sentUkeyClientInit{ + sendUkey2Alert(type: .badMessage) + } + lastError=error + protocolError() + } + } + + override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { + if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { + print("Transfer canceled") + try sendDisconnectionAndDisconnect() + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled)) + return + } + print(frame) + switch currentState{ + case .sentPairedKeyEncryption: + try processPairedKeyEncryption(frame: frame) + case .sentPairedKeyResult: + try processPairedKeyResult(frame: frame) + case .sentIntroduction: + try processConsent(frame: frame) + case .sendingFiles: + break + default: + assertionFailure("Unexpected state \(currentState)") + } + } + + override func protocolError() { + super.protocolError() + delegate?.outboundConnection(connection: self, failedWithError: lastError!) + } + + private func sendConnectionRequest() throws { + var frame=Location_Nearby_Connections_OfflineFrame() + frame.version = .v1 + frame.v1=Location_Nearby_Connections_V1Frame() + frame.v1.type = .connectionRequest + frame.v1.connectionRequest=Location_Nearby_Connections_ConnectionRequestFrame() + frame.v1.connectionRequest.endpointID=String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)! + frame.v1.connectionRequest.endpointName=Host.current().localizedName! + let endpointInfo=EndpointInfo(name: Host.current().localizedName!, deviceType: .computer) + frame.v1.connectionRequest.endpointInfo=endpointInfo.serialize() + frame.v1.connectionRequest.mediums=[.wifiLan] + sendFrameAsync(try frame.serializedData()) + } + + private func sendUkey2ClientInit() throws { + let domain=Domain.instance(curve: .EC256r1) + let (pubKey, privKey)=domain.makeKeyPair() + publicKey=pubKey + privateKey=privKey + + var finishFrame=Securegcm_Ukey2Message() + finishFrame.messageType = .clientFinish + var finish=Securegcm_Ukey2ClientFinished() + var pkey=Securemessage_GenericPublicKey() + pkey.type = .ecP256 + pkey.ecP256PublicKey=Securemessage_EcP256PublicKey() + pkey.ecP256PublicKey.x=Data(pubKey.w.x.asSignedBytes()) + pkey.ecP256PublicKey.y=Data(pubKey.w.y.asSignedBytes()) + finish.publicKey=try pkey.serializedData() + finishFrame.messageData=try finish.serializedData() + ukeyClientFinishMsgData=try finishFrame.serializedData() + + var frame=Securegcm_Ukey2Message() + frame.messageType = .clientInit + + var clientInit=Securegcm_Ukey2ClientInit() + clientInit.version=1 + clientInit.random=Data.randomData(length: 32) + clientInit.nextProtocol="AES_256_CBC-HMAC_SHA256" + var sha=SHA512() + sha.update(data: ukeyClientFinishMsgData!) + var commitment=Securegcm_Ukey2ClientInit.CipherCommitment() + commitment.commitment=Data(sha.finalize()) + commitment.handshakeCipher = .p256Sha512 + clientInit.cipherCommitments.append(commitment) + frame.messageData=try clientInit.serializedData() + + ukeyClientInitMsgData=try frame.serializedData() + sendFrameAsync(ukeyClientInitMsgData!) + currentState = .sentUkeyClientInit + } + + private func processUkey2ServerInit(frame:Securegcm_Ukey2Message, raw:Data) throws{ + ukeyServerInitMsgData=raw + guard frame.messageType == .serverInit else{ + sendUkey2Alert(type: .badMessageType) + throw NearbyError.ukey2 + } + let serverInit=try Securegcm_Ukey2ServerInit(serializedData: frame.messageData) + guard serverInit.version==1 else{ + sendUkey2Alert(type: .badVersion) + throw NearbyError.ukey2 + } + guard serverInit.random.count==32 else{ + sendUkey2Alert(type: .badRandom) + throw NearbyError.ukey2 + } + guard serverInit.handshakeCipher == .p256Sha512 else{ + sendUkey2Alert(type: .badHandshakeCipher) + throw NearbyError.ukey2 + } + + let serverKey=try Securemessage_GenericPublicKey(serializedData: serverInit.publicKey) + try finalizeKeyExchange(peerKey: serverKey) + sendFrameAsync(ukeyClientFinishMsgData!) + currentState = .sentUkeyClientFinish + + var resp=Location_Nearby_Connections_OfflineFrame() + resp.version = .v1 + resp.v1=Location_Nearby_Connections_V1Frame() + resp.v1.type = .connectionResponse + resp.v1.connectionResponse=Location_Nearby_Connections_ConnectionResponseFrame() + resp.v1.connectionResponse.response = .accept + resp.v1.connectionResponse.status=0 + resp.v1.connectionResponse.osInfo=Location_Nearby_Connections_OsInfo() + resp.v1.connectionResponse.osInfo.type = .apple + sendFrameAsync(try resp.serializedData()) + + encryptionDone=true + delegate?.outboundConnectionWasEstablished(connection: self) + } + + private func processConnectionResponse(frame:Location_Nearby_Connections_OfflineFrame) throws{ + #if DEBUG + print("connection response: \(frame)") + #endif + guard frame.version == .v1 else {throw NearbyError.protocolError("Unexpected offline frame version \(frame.version)")} + guard frame.v1.type == .connectionResponse else {throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)")} + guard frame.v1.connectionResponse.response == .accept else {throw NearbyError.protocolError("Connection was rejected by recipient")} + + var pairedEncryption=Sharing_Nearby_Frame() + pairedEncryption.version = .v1 + pairedEncryption.v1=Sharing_Nearby_V1Frame() + pairedEncryption.v1.type = .pairedKeyEncryption + pairedEncryption.v1.pairedKeyEncryption=Sharing_Nearby_PairedKeyEncryptionFrame() + pairedEncryption.v1.pairedKeyEncryption.secretIDHash=Data.randomData(length: 6) + pairedEncryption.v1.pairedKeyEncryption.signedData=Data.randomData(length: 72) + try sendTransferSetupFrame(pairedEncryption) + + currentState = .sentPairedKeyEncryption + } + + private func processPairedKeyEncryption(frame:Sharing_Nearby_Frame) throws{ + guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing } + var pairedResult=Sharing_Nearby_Frame() + pairedResult.version = .v1 + pairedResult.v1=Sharing_Nearby_V1Frame() + pairedResult.v1.type = .pairedKeyResult + pairedResult.v1.pairedKeyResult=Sharing_Nearby_PairedKeyResultFrame() + pairedResult.v1.pairedKeyResult.status = .unable + try sendTransferSetupFrame(pairedResult) + currentState = .sentPairedKeyResult + } + + private func processPairedKeyResult(frame:Sharing_Nearby_Frame) throws{ + guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing } + + var introduction=Sharing_Nearby_Frame() + introduction.version = .v1 + introduction.v1.type = .introduction + if urlsToSend.count==1 && urlsToSend[0].scheme != "file"{ + var meta=Sharing_Nearby_TextMetadata() + meta.type = .url + meta.textTitle=urlsToSend[0].host ?? "URL" + meta.size=Int64(urlsToSend[0].absoluteString.utf8.count) + meta.payloadID=Int64.random(in: Int64.min...Int64.max) + introduction.v1.introduction.textMetadata.append(meta) + }else{ + for url in urlsToSend{ + guard url.scheme=="file" else {continue} + var meta=Sharing_Nearby_FileMetadata() + meta.name=OutboundNearbyConnection.sanitizeFileName(name: url.lastPathComponent) + let attrs=try FileManager.default.attributesOfItem(atPath: url.path) + meta.size=(attrs[FileAttributeKey.size] as! NSNumber).int64Value + let typeID=try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier + meta.mimeType="application/octet-stream" + if let typeID=typeID{ + let type=UTType(typeID) + if let type=type, let mimeType=type.preferredMIMEType{ + meta.mimeType=mimeType + } + } + if meta.mimeType.starts(with: "image/"){ + meta.type = .image + }else if meta.mimeType.starts(with: "video/"){ + meta.type = .video + }else if(meta.mimeType.starts(with: "audio/")){ + meta.type = .audio + }else if(url.pathExtension.lowercased()=="apk"){ + meta.type = .app + }else{ + meta.type = .unknown + } + meta.payloadID=Int64.random(in: Int64.min...Int64.max) + queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: try FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0)) + introduction.v1.introduction.fileMetadata.append(meta) + totalBytesToSend+=meta.size + } + } + #if DEBUG + print("sent introduction: \(introduction)") + #endif + try sendTransferSetupFrame(introduction) + + currentState = .sentIntroduction + } + + private func processConsent(frame:Sharing_Nearby_Frame) throws{ + guard frame.version == .v1, frame.v1.type == .response else {throw NearbyError.requiredFieldMissing} + switch frame.v1.connectionResponse.status{ + case .accept: + currentState = .sendingFiles + delegate?.outboundConnectionTransferAccepted(connection: self) + try sendNextFileChunk() + case .reject, .unknown: + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userRejected)) + try sendDisconnectionAndDisconnect() + case .notEnoughSpace: + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .notEnoughSpace)) + try sendDisconnectionAndDisconnect() + case .timedOut: + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .timedOut)) + try sendDisconnectionAndDisconnect() + case .unsupportedAttachmentType: + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .unsupportedType)) + try sendDisconnectionAndDisconnect() + } + } + + private func sendNextFileChunk() throws{ + print("SEND NEXT: \(Thread.current)") + if cancelled{ + return + } + if currentTransfer==nil || currentTransfer?.currentOffset==currentTransfer?.totalBytes{ + if currentTransfer != nil && currentTransfer?.handle != nil{ + try currentTransfer?.handle?.close() + } + if queue.isEmpty{ + #if DEBUG + print("Disconnecting because all files have been transferred") + #endif + try sendDisconnectionAndDisconnect() + delegate?.outboundConnectionTransferFinished(connection: self) + return + } + currentTransfer=queue.removeFirst() + } + + guard let fileBuffer=try currentTransfer!.handle!.read(upToCount: 512*1024) else{ + throw NearbyError.inputOutput(cause: Errno.ioError) + } + + var transfer=Location_Nearby_Connections_PayloadTransferFrame() + transfer.packetType = .data + transfer.payloadChunk.offset=currentTransfer!.currentOffset + transfer.payloadChunk.flags=0 + transfer.payloadChunk.body=fileBuffer + transfer.payloadHeader.id=currentTransfer!.payloadID + transfer.payloadHeader.type = .file + transfer.payloadHeader.totalSize=Int64(currentTransfer!.totalBytes) + transfer.payloadHeader.isSensitive=false + currentTransfer!.currentOffset+=Int64(fileBuffer.count) + + var wrapper=Location_Nearby_Connections_OfflineFrame() + wrapper.version = .v1 + wrapper.v1=Location_Nearby_Connections_V1Frame() + wrapper.v1.type = .payloadTransfer + wrapper.v1.payloadTransfer=transfer + try encryptAndSendOfflineFrame(wrapper, completion: { + do{ + try self.sendNextFileChunk() + }catch{ + self.lastError=error + self.protocolError() + } + }) + #if DEBUG + print("sent file chunk, current transfer: \(String(describing: currentTransfer))") + #endif + totalBytesSent+=Int64(fileBuffer.count) + delegate?.outboundConnection(connection: self, transferProgress: Double(totalBytesSent)/Double(totalBytesToSend)) + + if currentTransfer!.currentOffset==currentTransfer!.totalBytes{ + // Signal end of file (yes, all this for one bit) + var transfer=Location_Nearby_Connections_PayloadTransferFrame() + transfer.packetType = .data + transfer.payloadChunk.offset=currentTransfer!.currentOffset + transfer.payloadChunk.flags=1 // <- this one here + transfer.payloadHeader.id=currentTransfer!.payloadID + transfer.payloadHeader.type = .file + transfer.payloadHeader.totalSize=Int64(currentTransfer!.totalBytes) + transfer.payloadHeader.isSensitive=false + + var wrapper=Location_Nearby_Connections_OfflineFrame() + wrapper.version = .v1 + wrapper.v1=Location_Nearby_Connections_V1Frame() + wrapper.v1.type = .payloadTransfer + wrapper.v1.payloadTransfer=transfer + try encryptAndSendOfflineFrame(wrapper) + #if DEBUG + print("sent EOF, current transfer: \(String(describing: currentTransfer))") + #endif + } + } + + private static func sanitizeFileName(name:String)->String{ + return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression) + } +} + +fileprivate struct OutgoingFileTransfer{ + let url:URL + let payloadID:Int64 + let handle:FileHandle? + let totalBytes:Int64 + var currentOffset:Int64 +} + +protocol OutboundNearbyConnectionDelegate{ + func outboundConnectionWasEstablished(connection:OutboundNearbyConnection) + func outboundConnection(connection:OutboundNearbyConnection, transferProgress:Double) + func outboundConnectionTransferAccepted(connection:OutboundNearbyConnection) + func outboundConnection(connection:OutboundNearbyConnection, failedWithError:Error) + func outboundConnectionTransferFinished(connection:OutboundNearbyConnection) +} diff --git a/NearDrop/Protobuf/device_to_device_messages.pb.swift b/NearbyShare/Protobuf/device_to_device_messages.pb.swift similarity index 100% rename from NearDrop/Protobuf/device_to_device_messages.pb.swift rename to NearbyShare/Protobuf/device_to_device_messages.pb.swift diff --git a/NearDrop/Protobuf/offline_wire_formats.pb.swift b/NearbyShare/Protobuf/offline_wire_formats.pb.swift similarity index 100% rename from NearDrop/Protobuf/offline_wire_formats.pb.swift rename to NearbyShare/Protobuf/offline_wire_formats.pb.swift diff --git a/NearDrop/Protobuf/securegcm.pb.swift b/NearbyShare/Protobuf/securegcm.pb.swift similarity index 100% rename from NearDrop/Protobuf/securegcm.pb.swift rename to NearbyShare/Protobuf/securegcm.pb.swift diff --git a/NearDrop/Protobuf/securemessage.pb.swift b/NearbyShare/Protobuf/securemessage.pb.swift similarity index 100% rename from NearDrop/Protobuf/securemessage.pb.swift rename to NearbyShare/Protobuf/securemessage.pb.swift diff --git a/NearDrop/Protobuf/ukey.pb.swift b/NearbyShare/Protobuf/ukey.pb.swift similarity index 100% rename from NearDrop/Protobuf/ukey.pb.swift rename to NearbyShare/Protobuf/ukey.pb.swift diff --git a/NearDrop/Protobuf/wire_format.pb.swift b/NearbyShare/Protobuf/wire_format.pb.swift similarity index 100% rename from NearDrop/Protobuf/wire_format.pb.swift rename to NearbyShare/Protobuf/wire_format.pb.swift diff --git a/NearDrop/ProtobufSource/device_to_device_messages.proto b/NearbyShare/ProtobufSource/device_to_device_messages.proto similarity index 100% rename from NearDrop/ProtobufSource/device_to_device_messages.proto rename to NearbyShare/ProtobufSource/device_to_device_messages.proto diff --git a/NearDrop/ProtobufSource/offline_wire_formats.proto b/NearbyShare/ProtobufSource/offline_wire_formats.proto similarity index 100% rename from NearDrop/ProtobufSource/offline_wire_formats.proto rename to NearbyShare/ProtobufSource/offline_wire_formats.proto diff --git a/NearDrop/ProtobufSource/securegcm.proto b/NearbyShare/ProtobufSource/securegcm.proto similarity index 100% rename from NearDrop/ProtobufSource/securegcm.proto rename to NearbyShare/ProtobufSource/securegcm.proto diff --git a/NearDrop/ProtobufSource/securemessage.proto b/NearbyShare/ProtobufSource/securemessage.proto similarity index 100% rename from NearDrop/ProtobufSource/securemessage.proto rename to NearbyShare/ProtobufSource/securemessage.proto diff --git a/NearDrop/ProtobufSource/ukey.proto b/NearbyShare/ProtobufSource/ukey.proto similarity index 100% rename from NearDrop/ProtobufSource/ukey.proto rename to NearbyShare/ProtobufSource/ukey.proto diff --git a/NearDrop/ProtobufSource/wire_format.proto b/NearbyShare/ProtobufSource/wire_format.proto similarity index 100% rename from NearDrop/ProtobufSource/wire_format.proto rename to NearbyShare/ProtobufSource/wire_format.proto diff --git a/PROTOCOL.md b/PROTOCOL.md index b484ee3..0b5a325 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -59,6 +59,7 @@ sequenceDiagram Server->>Client: UKEY2 ServerInit Client->>Server: UKEY2 ClientFinish Server->>Client: Connection response + Client->>Server: Connection response Note over Server, Client: All following packets are encrypted Server->>Client: Paired key encryption Client->>Server: Paired key encryption @@ -134,7 +135,7 @@ The **authentication string** is used for out-of-band key verification. Nearby S #### Connection response -After the key exchange is complete, the server sends one last plaintext message to the client: a connection response. It's a subtype of offline frame saying that the server has accepted the connection. All the following communication is encrypted and wrapped in the payload layer. +After the key exchange is complete, the server and client send each other one last plaintext message: a connection response. It's a subtype of offline frame saying that the other party has accepted the connection. All the following communication is encrypted and wrapped in the payload layer. ### The encryption layer diff --git a/ShareExtension/Assets.xcassets/Contents.json b/ShareExtension/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ShareExtension/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShareExtension/Assets.xcassets/NearDropIcon.imageset/16.png b/ShareExtension/Assets.xcassets/NearDropIcon.imageset/16.png new file mode 100644 index 0000000000000000000000000000000000000000..b4a80273fd576aa24ede74d3a5d7e6b80eb398f1 GIT binary patch literal 1005 zcmVE_*(zH_iH%XIjF1TY^MKh~= zLCaZ2G!*pB#^-#sPgLW+vWJQJi-k zIe&Q{hTcTqC~N*bXj+5Qi?Wv7hO>2Oz1I^);zz0ycdIJA9D^qg%~w!AU+h_FpQAg7 zt;$zj!p<&q%K4^+Z&mAO-uhtzQvd(}32;bRa{vGf6951U69E94oEQKA0uo6?K~y-6 zrII^uQ$Y}gzrDUruoKCS^B{-{Q9w=2zep2=6w#sp1r|^cAp)Y{AwWTqXy}lVA3;Y2 zqNhVbLJE#eY-3+P@6Msv>pTi*SZTB~M>}U{&mQ>ClBSjbA^>_8*H#Kx;!$F#Ygl5` zRtf~%0-hMw@zhodkXUp`;Co_zyF-0#7T*=VC+cf6R9>~IY;~xw&EUJj5>dZ4%lvi+ z-`C-=@!ZSSnDl7ugS#c>p0+|^K;&@l2zSmw49#je9|JHnIwBibE$fO2BP_>g8CW0SqXTShx0-7I3rN&Ma7kFG$xdk zZZ>GYSpcB4UWZ)xn$oR0?X5*h)n7*%%wGT5#|JWUE@j=z{_3IN2%v$?98VQYrhC)No7)V2C3>pXxKtn?*SZjY5 zupA((efk(xE*H`CIi*pN6o(h2f>E}oLaCWXgPOH}yc>PFcVEf((Zt;j$Yl}z+r-#J bAwYisiF7vb>uM`q00000NkvXXu0mjfI@HKm literal 0 HcmV?d00001 diff --git a/ShareExtension/Assets.xcassets/NearDropIcon.imageset/32.png b/ShareExtension/Assets.xcassets/NearDropIcon.imageset/32.png new file mode 100644 index 0000000000000000000000000000000000000000..21f8b8fe43f2db276a199911dbd190f3dba99a17 GIT binary patch literal 2080 zcmV+*2;cXKP)E_*(zH_iH%XIjF1TY^MKh~= zLCaZ2G!*pB#^-#sPgLW+vWJQJi-k zIe&Q{hTcTqC~N*bXj+5Qi?Wv7hO>2Oz1I^);zz0ycdIJA9D^qg%~w!AU+h_FpQAg7 zt;$zj!p<&q%K4^+Z&mAO-uhtzQvd(}32;bRa{vGf6951U69E94oEQKA21Q9kK~z|U z<(GeqTvZjvKj*$Tv-4xyvhLEgsWuy|#>OT7g|yPRreG-&LkhJmTL?x;_(N2pNh|+? zF%ZSXKTPlkm`JJ8k`&V2Z2W@?g;JVm4A52@6@uNRw7a(3?#|AzX5gjpkBRv|ApJvETl9) zyVdlq{FPX57f!nk0;(BO7C;KH%cX>F00$ywCw(hC;H&A5R9sdc{_x9Y#lS>QPx4D* zg&oE+hPuGzmRp$LesTNoi-4vPFzVz!4Yjuiy=Yl5dAa|$WvawduFC=^Zxne3RLm-(y-qX!R1) zLfBN%P}wploZepM`=dS@I<%>1sO@*?xNVJL;!7pY?kIEi&N36Xl-N3KI_1-+>3De5 z=gggDwhT(lZ?+@W?gNpcs@T9#_265h1%`{lL$5Z#i*vzK<;R=**s;Dq7=g2~q75sB zUw)yFgMT&bdp*FWx#N_djg{EG-Y2wR15oRd0OSB@sHjtpz8mu3l;cM?SJ?R}A6y^b zemS7x!EbIWvvG~bd`+1+8*t!s2oA+>)uhbb7;ou$pY#0(|7cCDQu5*0a#`3_QX9A9# z4cRwV=J8_<1_YkFqsr)zVXkI*;DsfQf8a1hi4!!_f74i$Kk}0+OK5s_Km_WPks*&m z<5kwJGR(|7?)hzvGqV;vLA+$U)m9#Ue86yMyvoQmhM6mtJ^PpV%VmcYK#CF@7va)( zS5<&|I|6AF%2dE-hduW1uCS_XcxyW3YtJq*U2{l5;u1}li2+|fY}mi6!a&vV)=bEK z`9!m9D66=uAeXBB4zR${oMMALL7dV z0#>$O=kxr%RVqcpD`x`kdU6pBm5KZNx%oODtRj&@Q^|JR&Kyus3%jo`@PlntqR27% zX29MTY6KP@8mq8jwa=3~3cU7Slc(Nj0`T=4i+p~hNEkXM-wxROe2oD3&RCVteAMUZ zT?O_{F7e#kL2S|WNJ1i2oXQtRd>-9ep;mVsc)iYJzptZ4z$lMQ)u=a>J)270u-0eO z&H@0IPrOtI5yV#>omvES?AcV}hug|rn791l{jj4=wg9-ML#z-kIo`b( z^6c>jzdjxyesbO-I8`2>s`2uvfboq*My@S@!h5qJ2Tn9O`Hv`;_Y!%lJTkGw^jyTA zn@WTY3jyG@kDyfrE50|oC>En3Xz1db&43h5d~c=M9wzx>71g-mh#_uPz94CxCo_XK zU7UD^7+jwLqg;LB+RiGV=^Pk?QzX6FOq^h%DNX~%B|TkYx;iitgAj+{#WV%ffRXma zpg`zQ(=LNd650krKTc3d5GMgrn+hd~n($kzn;q}b_K`TysBt+}(gNTXFP$>g%Ez(;gl313 zW#+q6GS+{mu%d0L68G3#y>NSQ%x&0X#d-2qfR7*cmII$BD!VCgj11g64p!Rn;g8Gzw;2ZRU^m+{Dw(*LMF@>*wDA8u=qgmjFdz5Eueh zCf^?a+p<6tSODgLe-ih?R-N>m@QF{=W4S-0|NYX;V`<-?t@STz#6xw2qA}F~0000< KMNUMnLSTYSUi&Nn literal 0 HcmV?d00001 diff --git a/ShareExtension/Assets.xcassets/NearDropIcon.imageset/Contents.json b/ShareExtension/Assets.xcassets/NearDropIcon.imageset/Contents.json new file mode 100644 index 0000000..7dbabe1 --- /dev/null +++ b/ShareExtension/Assets.xcassets/NearDropIcon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "16.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "32.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShareExtension/Base.lproj/Localizable.strings b/ShareExtension/Base.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..1dd36a7e62be67af5596a64ca596b26d6d9e4eba GIT binary patch literal 1488 zcmchXO-sW-5Qg7#e?<^_=^?!nN4JD5$;|BT zv+vC8_t#VtMVhN*Oc~#KausU%uT*3C&`ue)49kl?8O^`JTJV(<6Jwibs*x^q$Be>x zQr57T4c184ynlOR=EwN1m_K5b|K*wM0g5*`8;@h`PP6trp>{7(VTKB&_9(H{5>;0y zoqIH8$sf|k*#<~fXqoq5!owr{teS6qC zNvPu$`!`xcQ&Y$GXL+*P$vjO?Jz;V(B)W$qh33$AKGCV?JVe%61#d%L_ca=_N2ofC zJr85TzILN!dZGhcFOQuka$RS>Fig*`uJ&|s#ZEO8#*=WDR(y5cc8jNVx5L)Eh1=h_ Qh*b{vM8iq?YaPjd0N + + + + NFiles + + NSStringLocalizedFormatKey + %#@files@ + files + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d file + other + %d files + + + + diff --git a/ShareExtension/Base.lproj/ShareViewController.xib b/ShareExtension/Base.lproj/ShareViewController.xib new file mode 100644 index 0000000..9136b4e --- /dev/null +++ b/ShareExtension/Base.lproj/ShareViewController.xib @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ShareExtension/DeviceListCell.swift b/ShareExtension/DeviceListCell.swift new file mode 100644 index 0000000..9fc5405 --- /dev/null +++ b/ShareExtension/DeviceListCell.swift @@ -0,0 +1,26 @@ +// +// DeviceListCell.swift +// ShareExtension +// +// Created by Grishka on 20.09.2023. +// + +import Cocoa + +class DeviceListCell:NSCollectionViewItem { + public var clickHandler:(()->Void)? + + override func viewDidLoad() { + super.viewDidLoad() + let btn:NSButton=view as! NSButton + btn.isEnabled=true + btn.setButtonType(.momentaryPushIn) + btn.action=#selector(onClick) + btn.target=self + } + + @IBAction func onClick(_ sender:Any?){ + guard let handler=clickHandler else {return} + handler() + } +} diff --git a/ShareExtension/DeviceListCell.xib b/ShareExtension/DeviceListCell.xib new file mode 100644 index 0000000..dba0240 --- /dev/null +++ b/ShareExtension/DeviceListCell.xib @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ShareExtension/Info.plist b/ShareExtension/Info.plist new file mode 100644 index 0000000..a8147f3 --- /dev/null +++ b/ShareExtension/Info.plist @@ -0,0 +1,27 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + SUBQUERY ( + extensionItems, + $extensionItem, + SUBQUERY ( + $extensionItem.attachments, + $attachment, + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" + AND NOT (ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.folder") + ).@count == $extensionItem.attachments.@count +).@count > 0 + + NSExtensionPointIdentifier + com.apple.share-services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController + + + diff --git a/ShareExtension/ShareExtension.entitlements b/ShareExtension/ShareExtension.entitlements new file mode 100644 index 0000000..40b639e --- /dev/null +++ b/ShareExtension/ShareExtension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift new file mode 100644 index 0000000..f52310c --- /dev/null +++ b/ShareExtension/ShareViewController.swift @@ -0,0 +1,271 @@ +// +// ShareViewController.swift +// ShareExtension +// +// Created by Grishka on 12.09.2023. +// + +import Foundation +import Cocoa +import NearbyShare + +class ShareViewController: NSViewController, ShareExtensionDelegate{ + + private var urls:[URL]=[] + private var foundDevices:[RemoteDeviceInfo]=[] + private var chosenDevice:RemoteDeviceInfo? + private var lastError:Error? + + @IBOutlet var filesIcon:NSImageView? + @IBOutlet var filesLabel:NSTextField? + @IBOutlet var loadingOverlay:NSStackView? + @IBOutlet var largeProgress:NSProgressIndicator? + @IBOutlet var listView:NSCollectionView? + @IBOutlet var listViewWrapper:NSView? + @IBOutlet var contentWrap:NSView? + @IBOutlet var progressView:NSView? + @IBOutlet var progressDeviceIcon:NSImageView? + @IBOutlet var progressDeviceName:NSTextField? + @IBOutlet var progressProgressBar:NSProgressIndicator? + @IBOutlet var progressState:NSTextField? + @IBOutlet var progressDeviceIconWrap:NSView? + @IBOutlet var progressDeviceSecondaryIcon:NSImageView? + + override var nibName: NSNib.Name? { + return NSNib.Name("ShareViewController") + } + + override func loadView() { + super.loadView() + + // Insert code here to customize the view + let item = self.extensionContext!.inputItems[0] as! NSExtensionItem + if let attachments = item.attachments { + for attachment in attachments as NSArray{ + let provider=attachment as! NSItemProvider + provider.loadItem(forTypeIdentifier: kUTTypeURL as String) { data, err in + if let url=URL(dataRepresentation: data as! Data, relativeTo: nil, isAbsolute: false){ + self.urls.append(url) + if self.urls.count==attachments.count{ + DispatchQueue.main.async { + self.urlsReady() + } + } + } + } + } + } else { + let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) + self.extensionContext!.cancelRequest(withError: cancelError) + return + } + + contentWrap!.addSubview(listViewWrapper!) + contentWrap!.addSubview(loadingOverlay!) + contentWrap!.addSubview(progressView!) + progressView!.isHidden=true + + listViewWrapper!.translatesAutoresizingMaskIntoConstraints=false + loadingOverlay!.translatesAutoresizingMaskIntoConstraints=false + progressView!.translatesAutoresizingMaskIntoConstraints=false + NSLayoutConstraint.activate([ + NSLayoutConstraint(item: listViewWrapper!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0), + NSLayoutConstraint(item: listViewWrapper!, attribute: .height, relatedBy: .equal, toItem: contentWrap, attribute: .height, multiplier: 1, constant: 0), + + NSLayoutConstraint(item: loadingOverlay!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0), + NSLayoutConstraint(item: loadingOverlay!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0), + + NSLayoutConstraint(item: progressView!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0), + NSLayoutConstraint(item: progressView!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0) + ]) + + largeProgress!.startAnimation(nil) + let flowLayout=NSCollectionViewFlowLayout() + flowLayout.itemSize=NSSize(width: 75, height: 90) + flowLayout.sectionInset=NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + flowLayout.minimumInteritemSpacing=10 + flowLayout.minimumLineSpacing=10 + listView!.collectionViewLayout=flowLayout + listView!.dataSource=self + + progressDeviceIconWrap!.wantsLayer=true + progressDeviceIconWrap!.layer!.masksToBounds=false + } + + override func viewDidLoad(){ + super.viewDidLoad() + NearbyConnectionManager.shared.startDeviceDiscovery() + NearbyConnectionManager.shared.addShareExtensionDelegate(self) + } + + override func viewWillDisappear() { + if chosenDevice==nil{ + NearbyConnectionManager.shared.stopDeviceDiscovery() + } + NearbyConnectionManager.shared.removeShareExtensionDelegate(self) + } + + @IBAction func cancel(_ sender: AnyObject?) { + if let device=chosenDevice{ + NearbyConnectionManager.shared.cancelOutgoingTransfer(id: device.id!) + } + let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) + self.extensionContext!.cancelRequest(withError: cancelError) + } + + private func urlsReady(){ + for url in urls{ + if url.isFileURL{ + var isDirectory=UnsafeMutablePointer.allocate(capacity: 1) + if FileManager.default.fileExists(atPath: url.path, isDirectory: isDirectory) && isDirectory.pointee.boolValue{ + print("Canceling share request because URL \(url) is a directory") + let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) + self.extensionContext!.cancelRequest(withError: cancelError) + return + } + } + } + if urls.count==1{ + if urls[0].isFileURL{ + filesLabel!.stringValue=urls[0].lastPathComponent + filesIcon!.image=NSWorkspace.shared.icon(forFile: urls[0].path) + }else if urls[0].scheme=="http" || urls[0].scheme=="https"{ + filesLabel!.stringValue=urls[0].absoluteString + filesIcon!.image=NSImage(named: NSImage.networkName) + } + }else{ + filesLabel!.stringValue=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), urls.count) + filesIcon!.image=NSImage(named: NSImage.multipleDocumentsName) + } + } + + func addDevice(device: RemoteDeviceInfo) { + if foundDevices.isEmpty{ + loadingOverlay?.animator().isHidden=true + } + foundDevices.append(device) + listView?.animator().insertItems(at: [[0, foundDevices.count-1]]) + } + + func removeDevice(id: String){ + if chosenDevice != nil{ + return + } + for i in foundDevices.indices{ + if foundDevices[i].id==id{ + foundDevices.remove(at: i) + listView?.animator().deleteItems(at: [[0, i]]) + break + } + } + if foundDevices.isEmpty{ + loadingOverlay?.animator().isHidden=false + } + } + + func connectionWasEstablished(pinCode: String) { + progressState?.stringValue=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [pinCode]) + progressProgressBar?.isIndeterminate=false + progressProgressBar?.maxValue=1000 + progressProgressBar?.doubleValue=0 + } + + func connectionFailed(with error: Error) { + progressProgressBar?.isIndeterminate=false + progressProgressBar?.maxValue=1000 + progressProgressBar?.doubleValue=0 + lastError=error + if let ne=(error as? NearbyError), case let .canceled(reason)=ne{ + switch reason{ + case .userRejected: + progressState?.stringValue=NSLocalizedString("TransferDeclined", value: "Declined", comment: "") + case .userCanceled: + progressState?.stringValue=NSLocalizedString("TransferCanceled", value: "Canceled", comment: "") + case .notEnoughSpace: + progressState?.stringValue=NSLocalizedString("NotEnoughSpace", value: "Not enough disk space", comment: "") + case .unsupportedType: + progressState?.stringValue=NSLocalizedString("UnsupportedType", value: "Attachment type not supported", comment: "") + case .timedOut: + progressState?.stringValue=NSLocalizedString("TransferTimedOut", value: "Timed out", comment: "") + } + progressDeviceSecondaryIcon?.isHidden=false + dismissDelayed() + }else{ + let alert=NSAlert(error: error) + alert.beginSheetModal(for: view.window!) { resp in + self.extensionContext!.cancelRequest(withError: error) + } + } + } + + func transferAccepted() { + progressState?.stringValue=NSLocalizedString("Sending", value: "Sending...", comment: "") + } + + func transferProgress(progress: Double) { + progressProgressBar!.doubleValue=progress*progressProgressBar!.maxValue + } + + func transferFinished() { + progressState?.stringValue=NSLocalizedString("TransferFinished", value: "Transfer finished", comment: "") + dismissDelayed() + } + + func selectDevice(device:RemoteDeviceInfo){ + NearbyConnectionManager.shared.stopDeviceDiscovery() + listViewWrapper?.animator().isHidden=true + progressView?.animator().isHidden=false + progressDeviceName?.stringValue=device.name + progressDeviceIcon?.image=imageForDeviceType(type: device.type) + progressProgressBar?.startAnimation(nil) + progressState?.stringValue=NSLocalizedString("Connecting", value: "Connecting...", comment: "") + chosenDevice=device + NearbyConnectionManager.shared.startOutgoingTransfer(deviceID: device.id!, delegate: self, urls: urls) + } + + private func dismissDelayed(){ + DispatchQueue.main.asyncAfter(deadline: .now()+2.0){ + if let error=self.lastError{ + self.extensionContext!.cancelRequest(withError: error) + }else{ + self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil) + } + } + } +} + +fileprivate func imageForDeviceType(type:RemoteDeviceInfo.DeviceType)->NSImage{ + let imageName:String + switch type{ + case .tablet: + imageName="com.apple.ipad" + case .computer: + imageName="com.apple.macbookpro-13-unibody" + default: // also .phone + imageName="com.apple.iphone" + } + return NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/\(imageName).icns")! +} + +extension ShareViewController:NSCollectionViewDataSource{ + func numberOfSections(in collectionView: NSCollectionView) -> Int { + return 1 + } + + func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { + return foundDevices.count + } + + func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { + let item=collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DeviceListCell"), for: indexPath) + guard let collectionViewItem = item as? DeviceListCell else {return item} + let device=foundDevices[indexPath[1]] + collectionViewItem.textField?.stringValue=device.name + collectionViewItem.imageView?.image=imageForDeviceType(type: device.type) + // TODO maybe there's a better way to handle clicks on collection view items? I'm still new to Apple's way of doing UIs so I may do dumb shit occasionally + collectionViewItem.clickHandler={ + self.selectDevice(device: device) + } + return collectionViewItem + } +} diff --git a/ShareExtension/ru.lproj/Localizable.strings b/ShareExtension/ru.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..9f22da11b26d15e2938f2688fe558f8286abda8d GIT binary patch literal 1532 zcmchX%TB^j5Qb+lE1tlOT1nWj07AeE&~gz(qX81|0gxh^Xdzroe0lYoLn4*6CCxds zb9#C{{`qIVzw(k5PkJ(Eo-n_2O(l{w&kI=+`H)u$iG@U-%<|;B5hRwEeWErT6KJ-TxKC7Z&OS7gRa?YZ;q<>MouW>I zQ6hs~=j02QON-t*dX3qCq>6x!*8AhdQR;bzt3+! zG1^Dxx($6|K@Be9Gtv>gcfsAR9<6KCp)7Hkibhw2u8bS+>1>{~h}2!rFlL`<=a>2i DDh=c* literal 0 HcmV?d00001 diff --git a/ShareExtension/ru.lproj/Localizable.stringsdict b/ShareExtension/ru.lproj/Localizable.stringsdict new file mode 100644 index 0000000..2cdb428 --- /dev/null +++ b/ShareExtension/ru.lproj/Localizable.stringsdict @@ -0,0 +1,26 @@ + + + + + NFiles + + NSStringLocalizedFormatKey + %#@files@ + files + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d файл + few + %d файла + many + %d файлов + other + %d файлов + + + + diff --git a/ShareExtension/ru.lproj/ShareViewController.strings b/ShareExtension/ru.lproj/ShareViewController.strings new file mode 100644 index 0000000..bb56687 --- /dev/null +++ b/ShareExtension/ru.lproj/ShareViewController.strings @@ -0,0 +1,12 @@ + +/* Class = "NSTextFieldCell"; title = "NearDrop"; ObjectID = "0xp-rC-2gr"; */ +"0xp-rC-2gr.title" = "NearDrop"; + +/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "6Up-t3-mwm"; */ +"6Up-t3-mwm.title" = "Отменить"; + +/* Class = "NSTextFieldCell"; title = "Looking for devices..."; ObjectID = "NaJ-Wx-Pim"; */ +"NaJ-Wx-Pim.title" = "Ищу устройства..."; + +/* Class = "NSTextFieldCell"; title = "If you don't see your device, open \"Files by Google\" app and tap \"Receive\" on the Nearby Share tab."; ObjectID = "vla-gF-eJo"; */ +"vla-gF-eJo.title" = "Если Вы не видите своё устройство, откройте приложение \"Google Files\" и нажмите \"Получить\" на вкладке Обмен.";