An experimental share extension to send files and too much refactoring

closes #4
This commit is contained in:
Grishka
2023-09-26 03:22:23 +03:00
parent 22a2fff54b
commit 8f0eccd7b6
39 changed files with 2158 additions and 235 deletions

View File

@@ -7,26 +7,63 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; 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 */; }; 69D2C32D29E77F2200EC7E30 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32B29E77F2200EC7E30 /* Localizable.strings */; };
69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */; }; 69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */; };
69D2C33829E78DF400EC7E30 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C33629E78DF400EC7E30 /* Localizable.stringsdict */; }; 69D2C33829E78DF400EC7E30 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C33629E78DF400EC7E30 /* Localizable.stringsdict */; };
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; }; 69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; };
69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; 69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
69DA9A1F29E0C0B300A442DA /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; }; 69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
69DA9A2129E0CC4E00A442DA /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */; }; 69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
69DA9A2329E17F0400A442DA /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; }; 69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
69DA9A2629E189EF00A442DA /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 69DA9A2529E189EF00A442DA /* SwiftProtobuf */; }; 69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
69DA9A2E29E18CB500A442DA /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; }; 69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
69DA9A2F29E18CB500A442DA /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; }; 69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
69DA9A3029E18CB500A442DA /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; }; 69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
69DA9A3129E18CB500A442DA /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; }; 69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
69DA9A3229E18CB500A442DA /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; }; 69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
69DA9A3329E18CB500A442DA /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; }; 69DCF4932AB70E9700CBE2CC /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */; };
69DA9A3629E1994C00A442DA /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = 69DA9A3529E1994C00A442DA /* SwiftECC */; }; 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 */ /* 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 */ /* Begin PBXCopyFilesBuildPhase section */
698DFAFF29E353220064F247 /* Embed Foundation Extensions */ = { 698DFAFF29E353220064F247 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
@@ -34,10 +71,43 @@
dstPath = ""; dstPath = "";
dstSubfolderSpec = 13; dstSubfolderSpec = 13;
files = ( files = (
699DEBAE2AB0573200115D22 /* ShareExtension.appex in Embed Foundation Extensions */,
); );
name = "Embed Foundation Extensions"; name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0; 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 */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@@ -45,12 +115,26 @@
3226184E2A51E10600B06FD1 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; }; 3226184E2A51E10600B06FD1 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
322618502A51EB8A00B06FD1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 322618502A51EB8A00B06FD1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
322618512A51EB8A00B06FD1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; }; 322618512A51EB8A00B06FD1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
691F53B92ABB70840089FD92 /* DeviceListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListCell.swift; sourceTree = "<group>"; };
691F53BA2ABB70840089FD92 /* DeviceListCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeviceListCell.xib; sourceTree = "<group>"; };
691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundNearbyConnection.swift; sourceTree = "<group>"; };
691F53C42AC257A30089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/ShareViewController.strings; sourceTree = "<group>"; };
691F53C62AC2594E0089FD92 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
691F53C82AC259630089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
691F53CA2AC2599B0089FD92 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = Base; path = Base.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
691F53CC2AC259A20089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
698DFAE529E2F91A0064F247 /* NearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyConnection.swift; sourceTree = "<group>"; }; 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyConnection.swift; sourceTree = "<group>"; };
698DFAED29E353220064F247 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; 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; }; 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 = "<group>"; }; 698DFB0029E362140064F247 /* NearDrop-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NearDrop-Bridging-Header.h"; sourceTree = "<group>"; };
698DFB0129E362140064F247 /* NDNotificationCenterHackery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NDNotificationCenterHackery.h; sourceTree = "<group>"; }; 698DFB0129E362140064F247 /* NDNotificationCenterHackery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NDNotificationCenterHackery.h; sourceTree = "<group>"; };
698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NDNotificationCenterHackery.m; sourceTree = "<group>"; }; 698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NDNotificationCenterHackery.m; sourceTree = "<group>"; };
699B03442AB5FBA300E0D718 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
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 = "<group>"; };
699DEBA82AB0573200115D22 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ShareViewController.xib; sourceTree = "<group>"; };
699DEBAA2AB0573200115D22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
699DEBAB2AB0573200115D22 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
69D2C32C29E77F2200EC7E30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; }; 69D2C32C29E77F2200EC7E30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
69D2C32E29E7898C00EC7E30 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; }; 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
69D2C33029E789AF00EC7E30 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; }; 69D2C33029E789AF00EC7E30 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -69,6 +153,7 @@
69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = "<group>"; }; 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = "<group>"; };
69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; }; 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; };
69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; }; 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; };
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 = "<group>"; }; A3930F0529EAB2D1008F891D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
A3930F0629EAB2D1008F891D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; }; A3930F0629EAB2D1008F891D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
B670906E2A6D234D00DB8273 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; B670906E2A6D234D00DB8273 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@@ -76,12 +161,28 @@
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
699DEB9E2AB0573200115D22 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
69DA9A0B29E0BF5100A442DA /* Frameworks */ = { 69DA9A0B29E0BF5100A442DA /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
69DA9A3629E1994C00A442DA /* SwiftECC in Frameworks */, 69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */,
69DA9A2629E189EF00A442DA /* SwiftProtobuf in Frameworks */, );
runOnlyForDeploymentPostprocessing = 0;
};
69DCF47F2AB70D0600CBE2CC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */,
69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -97,10 +198,28 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
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 = "<group>";
};
69DA9A0529E0BF5100A442DA = { 69DA9A0529E0BF5100A442DA = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
69DA9A1029E0BF5100A442DA /* NearDrop */, 69DA9A1029E0BF5100A442DA /* NearDrop */,
699DEBA22AB0573200115D22 /* ShareExtension */,
69DCF4822AB70D0600CBE2CC /* NearbyShare */,
698DFAEC29E353220064F247 /* Frameworks */, 698DFAEC29E353220064F247 /* Frameworks */,
69DA9A0F29E0BF5100A442DA /* Products */, 69DA9A0F29E0BF5100A442DA /* Products */,
); );
@@ -110,6 +229,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
69DA9A0E29E0BF5100A442DA /* NearDrop.app */, 69DA9A0E29E0BF5100A442DA /* NearDrop.app */,
699DEBA12AB0573200115D22 /* ShareExtension.appex */,
69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -117,17 +238,12 @@
69DA9A1029E0BF5100A442DA /* NearDrop */ = { 69DA9A1029E0BF5100A442DA /* NearDrop */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
69DA9A2729E18CB500A442DA /* Protobuf */,
69DA9A1129E0BF5100A442DA /* AppDelegate.swift */, 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */,
69DA9A1329E0BF5200A442DA /* Assets.xcassets */, 69DA9A1329E0BF5200A442DA /* Assets.xcassets */,
69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */,
69D2C33629E78DF400EC7E30 /* Localizable.stringsdict */, 69D2C33629E78DF400EC7E30 /* Localizable.stringsdict */,
69D2C32B29E77F2200EC7E30 /* Localizable.strings */, 69D2C32B29E77F2200EC7E30 /* Localizable.strings */,
69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */,
698DFB0129E362140064F247 /* NDNotificationCenterHackery.h */, 698DFB0129E362140064F247 /* NDNotificationCenterHackery.h */,
698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */, 698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */,
698DFAE529E2F91A0064F247 /* NearbyConnection.swift */,
69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */,
69DA9A1829E0BF5200A442DA /* NearDrop.entitlements */, 69DA9A1829E0BF5200A442DA /* NearDrop.entitlements */,
698DFB0029E362140064F247 /* NearDrop-Bridging-Header.h */, 698DFB0029E362140064F247 /* NearDrop-Bridging-Header.h */,
69D2C32E29E7898C00EC7E30 /* MainMenu.xib */, 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */,
@@ -148,9 +264,51 @@
path = Protobuf; path = Protobuf;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
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 = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
69DCF47D2AB70D0600CBE2CC /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget 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 */ = { 69DA9A0D29E0BF5100A442DA /* NearDrop */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 69DA9A1B29E0BF5200A442DA /* Build configuration list for PBXNativeTarget "NearDrop" */; buildConfigurationList = 69DA9A1B29E0BF5200A442DA /* Build configuration list for PBXNativeTarget "NearDrop" */;
@@ -159,19 +317,42 @@
69DA9A0B29E0BF5100A442DA /* Frameworks */, 69DA9A0B29E0BF5100A442DA /* Frameworks */,
69DA9A0C29E0BF5100A442DA /* Resources */, 69DA9A0C29E0BF5100A442DA /* Resources */,
698DFAFF29E353220064F247 /* Embed Foundation Extensions */, 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 = ( buildRules = (
); );
dependencies = ( dependencies = (
); );
name = NearDrop; name = NearbyShare;
packageProductDependencies = ( packageProductDependencies = (
69DA9A2529E189EF00A442DA /* SwiftProtobuf */, 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */,
69DA9A3529E1994C00A442DA /* SwiftECC */, 69DCF49B2AB8C58500CBE2CC /* SwiftECC */,
); );
productName = NearDrop; productName = NearbyShare;
productReference = 69DA9A0E29E0BF5100A442DA /* NearDrop.app */; productReference = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.library.dynamic";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@@ -183,10 +364,16 @@
LastSwiftUpdateCheck = 1430; LastSwiftUpdateCheck = 1430;
LastUpgradeCheck = 1430; LastUpgradeCheck = 1430;
TargetAttributes = { TargetAttributes = {
699DEBA02AB0573200115D22 = {
CreatedOnToolsVersion = 14.3.1;
};
69DA9A0D29E0BF5100A442DA = { 69DA9A0D29E0BF5100A442DA = {
CreatedOnToolsVersion = 14.3; CreatedOnToolsVersion = 14.3;
LastSwiftMigration = 1430; LastSwiftMigration = 1430;
}; };
69DCF4802AB70D0600CBE2CC = {
CreatedOnToolsVersion = 14.3.1;
};
}; };
}; };
buildConfigurationList = 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "NearDrop" */; buildConfigurationList = 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "NearDrop" */;
@@ -212,11 +399,25 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
69DA9A0D29E0BF5100A442DA /* NearDrop */, 69DA9A0D29E0BF5100A442DA /* NearDrop */,
699DEBA02AB0573200115D22 /* ShareExtension */,
69DCF4802AB70D0600CBE2CC /* NearbyShare */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase 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 */ = { 69DA9A0C29E0BF5100A442DA /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -231,28 +432,90 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase 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 */ = { 69DA9A0A29E0BF5100A442DA /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
69DA9A2E29E18CB500A442DA /* ukey.pb.swift in Sources */,
698DFB0329E362140064F247 /* NDNotificationCenterHackery.m 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 */, 69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */,
69DA9A2F29E18CB500A442DA /* wire_format.pb.swift in Sources */, );
69DA9A3329E18CB500A442DA /* securemessage.pb.swift in Sources */, runOnlyForDeploymentPostprocessing = 0;
69DA9A2129E0CC4E00A442DA /* Data+Extensions.swift in Sources */, };
69DA9A3229E18CB500A442DA /* securegcm.pb.swift in Sources */, 69DCF47E2AB70D0600CBE2CC /* Sources */ = {
698DFAE629E2F91A0064F247 /* NearbyConnection.swift in 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; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXSourcesBuildPhase section */ /* 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 */ /* Begin PBXVariantGroup section */
691F53C52AC2594E0089FD92 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
691F53C62AC2594E0089FD92 /* Base */,
691F53C82AC259630089FD92 /* ru */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
691F53C92AC2599B0089FD92 /* Localizable.stringsdict */ = {
isa = PBXVariantGroup;
children = (
691F53CA2AC2599B0089FD92 /* Base */,
691F53CC2AC259A20089FD92 /* ru */,
);
name = Localizable.stringsdict;
sourceTree = "<group>";
};
699DEBA72AB0573200115D22 /* ShareViewController.xib */ = {
isa = PBXVariantGroup;
children = (
699DEBA82AB0573200115D22 /* Base */,
691F53C42AC257A30089FD92 /* ru */,
);
name = ShareViewController.xib;
sourceTree = "<group>";
};
69D2C32B29E77F2200EC7E30 /* Localizable.strings */ = { 69D2C32B29E77F2200EC7E30 /* Localizable.strings */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
children = ( children = (
@@ -282,6 +545,58 @@
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration 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 */ = { 69DA9A1929E0BF5200A442DA /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@@ -333,7 +648,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.3; MACOSX_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
@@ -388,7 +703,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.3; MACOSX_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = macosx; SDKROOT = macosx;
@@ -400,6 +715,7 @@
69DA9A1C29E0BF5200A442DA /* Debug */ = { 69DA9A1C29E0BF5200A442DA /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
@@ -431,6 +747,7 @@
69DA9A1D29E0BF5200A442DA /* Release */ = { 69DA9A1D29E0BF5200A442DA /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
@@ -458,9 +775,46 @@
}; };
name = Release; 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 */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList 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" */ = { 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "NearDrop" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
@@ -479,6 +833,15 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */ = {
isa = XCConfigurationList;
buildConfigurations = (
69DCF4872AB70D0600CBE2CC /* Debug */,
69DCF4882AB70D0600CBE2CC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
@@ -501,12 +864,12 @@
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
69DA9A2529E189EF00A442DA /* SwiftProtobuf */ = { 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */; package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = SwiftProtobuf; productName = SwiftProtobuf;
}; };
69DA9A3529E1994C00A442DA /* SwiftECC */ = { 69DCF49B2AB8C58500CBE2CC /* SwiftECC */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */; package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */;
productName = SwiftECC; productName = SwiftECC;

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "699DEBA02AB0573200115D22"
BuildableName = "ShareExtension.appex"
BlueprintName = "ShareExtension"
ReferencedContainer = "container:NearDrop.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "69DA9A0D29E0BF5100A442DA"
BuildableName = "NearDrop.app"
BlueprintName = "NearDrop"
ReferencedContainer = "container:NearDrop.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "69DA9A0D29E0BF5100A442DA"
BuildableName = "NearDrop.app"
BlueprintName = "NearDrop"
ReferencedContainer = "container:NearDrop.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "69DA9A0D29E0BF5100A442DA"
BuildableName = "NearDrop.app"
BlueprintName = "NearDrop"
ReferencedContainer = "container:NearDrop.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -7,12 +7,12 @@
import Cocoa import Cocoa
import UserNotifications import UserNotifications
import NearbyShare
@main @main
class AppDelegate: NSObject, NSApplicationDelegate{ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, MainAppDelegate{
private var connectionManager:NearbyConnectionManager?
private var statusItem:NSStatusItem? private var statusItem:NSStatusItem?
private var activeIncomingTransfers:[String:TransferInfo]=[:]
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
let menu=NSMenu() let menu=NSMenu()
@@ -32,10 +32,12 @@ class AppDelegate: NSObject, NSApplicationDelegate{
} }
} }
} }
nc.delegate=self
let incomingTransfersCategory=NDNotificationCenterHackery.hackedNotificationCategory() let incomingTransfersCategory=NDNotificationCenterHackery.hackedNotificationCategory()
let errorsCategory=UNNotificationCategory(identifier: "ERRORS", actions: [], intentIdentifiers: []) let errorsCategory=UNNotificationCategory(identifier: "ERRORS", actions: [], intentIdentifiers: [])
nc.setNotificationCategories([incomingTransfersCategory, errorsCategory]) nc.setNotificationCategories([incomingTransfersCategory, errorsCategory])
connectionManager=NearbyConnectionManager() NearbyConnectionManager.shared.mainAppDelegate=self
NearbyConnectionManager.shared.becomeVisible()
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_ aNotification: Notification) {
@@ -60,5 +62,66 @@ class AppDelegate: NSObject, NSApplicationDelegate{
NSApplication.shared.terminate(nil) 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
}

View File

@@ -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..<alphabet.count)])
}
return id
}
private func initMDNS(){
let nameBytes:[UInt8]=[
0x23, // PCP
endpointID[0], endpointID[1], endpointID[2], endpointID[3],
0xFC, 0x9F, 0x5E, // Service ID hash
0, 0
]
let name=Data(nameBytes).urlSafeBase64EncodedString()
// 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]=[3 << 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
let hostName=Host.current().localizedName!
let hostNameChars=hostName.utf8
endpointInfo.append(UInt8(hostNameChars.count))
for (i, ch) in hostNameChars.enumerated(){
guard i<256 else {break}
endpointInfo.append(UInt8(ch))
}
let port:Int32=Int32(tcpListener.port!.rawValue)
mdnsService=NetService(domain: "", type: "_FC9F5ED42C8A._tcp.", name: name, port: port)
mdnsService?.delegate=self
mdnsService?.includesPeerToPeer=true
mdnsService?.setTXTRecord(NetService.data(fromTXTRecord: [
"n": Data(endpointInfo).urlSafeBase64EncodedString().data(using: .utf8)!
]))
mdnsService?.publish()
}
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) {
let notificationContent=UNMutableNotificationContent()
notificationContent.title="NearDrop"
notificationContent.subtitle=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [connection.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": connection.id]
NDNotificationCenterHackery.removeDefaultAction(notificationContent)
let notificationReq=UNNotificationRequest(identifier: "transfer_"+connection.id, content: notificationContent, trigger: nil)
UNUserNotificationCenter.current().add(notificationReq)
}
func connectionWasTerminated(connection:InboundNearbyConnection, error:Error?){
activeConnections.removeValue(forKey: connection.id)
if let error=error{
let notificationContent=UNMutableNotificationContent()
notificationContent.title=String(format: NSLocalizedString("TransferError", value: "Failed to receive files from %@", comment: ""), arguments: [connection.remoteDeviceInfo!.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: "")
}
}else{
notificationContent.body=error.localizedDescription
}
notificationContent.categoryIdentifier="ERRORS"
UNUserNotificationCenter.current().add(UNNotificationRequest(identifier: "transferError_"+connection.id, content: notificationContent, trigger: nil))
}
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["transfer_"+connection.id])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
activeConnections[response.notification.request.content.userInfo["transferID"]! as! String]?.submitUserConsent(accepted: response.actionIdentifier=="ACCEPT")
completionHandler()
}
}

View File

@@ -28,4 +28,20 @@ extension Data{
} }
return 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)
}
} }

View File

@@ -63,8 +63,10 @@ class InboundNearbyConnection: NearbyConnection{
} }
}catch{ }catch{
lastError=error lastError=error
print("Deserialization error: \(error)") print("Deserialization error: \(error) in state \(currentState)")
#if !DEBUG
protocolError() protocolError()
#endif
} }
} }
@@ -279,7 +281,7 @@ class InboundNearbyConnection: NearbyConnection{
destinationURL: dest) destinationURL: dest)
transferredFiles[file.payloadID]=info 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 { DispatchQueue.main.async {
self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self)
} }

View File

@@ -16,6 +16,7 @@ import BigInt
class NearbyConnection{ class NearbyConnection{
internal static let SANE_FRAME_LENGTH=5*1024*1024 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 let connection:NWConnection
internal var remoteDeviceInfo:RemoteDeviceInfo? internal var remoteDeviceInfo:RemoteDeviceInfo?
@@ -52,6 +53,7 @@ class NearbyConnection{
func start(){ func start(){
connection.stateUpdateHandler={state in connection.stateUpdateHandler={state in
if case .ready = state { if case .ready = state {
self.connectionReady()
self.receiveFrameAsync() self.receiveFrameAsync()
} else if case .failed(let err) = state { } else if case .failed(let err) = state {
self.lastError=err self.lastError=err
@@ -59,9 +61,12 @@ class NearbyConnection{
self.handleConnectionClosure() self.handleConnectionClosure()
} }
} }
connection.start(queue: .global(qos: .utility)) //connection.start(queue: .global(qos: .utility))
connection.start(queue: NearbyConnection.dispatchQueue)
} }
func connectionReady(){}
internal func handleConnectionClosure(){ internal func handleConnectionClosure(){
print("Connection closed") 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) var lengthPrefixedData=Data(capacity: frame.count+4)
let length:Int=frame.count let length:Int=frame.count
lengthPrefixedData.append(contentsOf: [ lengthPrefixedData.append(contentsOf: [
UInt8(length >> 24), UInt8(truncatingIfNeeded: length >> 24),
UInt8(length >> 16), UInt8(truncatingIfNeeded: length >> 16),
UInt8(length >> 8), UInt8(truncatingIfNeeded: length >> 8),
UInt8(length) UInt8(truncatingIfNeeded: length)
]) ])
lengthPrefixedData.append(frame) lengthPrefixedData.append(frame)
connection.send(content: lengthPrefixedData, completion: .contentProcessed({ error in 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() var d2dMsg=Securegcm_DeviceToDeviceMessage()
serverSeq+=1 serverSeq+=1
d2dMsg.sequenceNumber=serverSeq d2dMsg.sequenceNumber=serverSeq
@@ -185,7 +195,7 @@ class NearbyConnection{
var smsg=Securemessage_SecureMessage() var smsg=Securemessage_SecureMessage()
smsg.headerAndBody=try hb.serializedData() smsg.headerAndBody=try hb.serializedData()
smsg.signature=Data(HMAC<SHA256>.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!)) smsg.signature=Data(HMAC<SHA256>.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!))
sendFrameAsync(try smsg.serializedData()) sendFrameAsync(try smsg.serializedData(), completion: completion)
} }
internal func sendTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{ internal func sendTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{
@@ -275,6 +285,9 @@ class NearbyConnection{
try processFileChunk(frame: payloadTransfer) try processFileChunk(frame: payloadTransfer)
} }
}else if case .keepAlive = offlineFrame.v1.type{ }else if case .keepAlive = offlineFrame.v1.type{
#if DEBUG
print("Sent keep-alive")
#endif
sendKeepAlive(ack: true) sendKeepAlive(ack: true)
}else{ }else{
print("Unhandled offline frame encrypted: \(offlineFrame)") 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{ struct InternalFileInfo{
let meta:FileMetadata let meta:FileMetadata
let payloadID:Int64 let payloadID:Int64

View File

@@ -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..<alphabet.count)])
}
return id
}
private func initMDNS(){
let nameBytes:[UInt8]=[
0x23, // PCP
endpointID[0], endpointID[1], endpointID[2], endpointID[3],
0xFC, 0x9F, 0x5E, // Service ID hash
0, 0
]
let name=Data(nameBytes).urlSafeBase64EncodedString()
let endpointInfo=EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
let port:Int32=Int32(tcpListener.port!.rawValue)
mdnsService=NetService(domain: "", type: "_FC9F5ED42C8A._tcp.", name: name, port: port)
mdnsService?.delegate=self
mdnsService?.includesPeerToPeer=true
mdnsService?.setTXTRecord(NetService.data(fromTXTRecord: [
"n": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!
]))
mdnsService?.publish()
}
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) {
guard let delegate=mainAppDelegate else {return}
delegate.obtainUserConsent(for: transfer, from: device)
}
func connectionWasTerminated(connection:InboundNearbyConnection, error:Error?){
guard let delegate=mainAppDelegate else {return}
delegate.incomingTransfer(id: connection.id, didFinishWith: error)
activeConnections.removeValue(forKey: connection.id)
}
public func submitUserConsent(transferID:String, accept:Bool){
guard let conn=activeConnections[transferID] else {return}
conn.submitUserConsent(accepted: accept)
}
public func startDeviceDiscovery(){
if discoveryRefCount==0{
foundServices.removeAll()
if browser==nil{
browser=NWBrowser(for: .bonjourWithTXTRecord(type: "_FC9F5ED42C8A._tcp.", domain: nil), using: .tcp)
browser?.browseResultsChangedHandler={newResults, changes in
for change in changes{
switch change{
case let .added(res):
self.maybeAddFoundDevice(service: res)
case let .removed(res):
self.maybeRemoveFoundDevice(service: res)
default:
break
}
}
}
}
browser?.start(queue: .main)
}
discoveryRefCount+=1
}
public func stopDeviceDiscovery(){
discoveryRefCount-=1
assert(discoveryRefCount>=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)
}
}

View File

@@ -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)
}

View File

@@ -59,6 +59,7 @@ sequenceDiagram
Server->>Client: UKEY2 ServerInit Server->>Client: UKEY2 ServerInit
Client->>Server: UKEY2 ClientFinish Client->>Server: UKEY2 ClientFinish
Server->>Client: Connection response Server->>Client: Connection response
Client->>Server: Connection response
Note over Server, Client: All following packets are encrypted Note over Server, Client: All following packets are encrypted
Server->>Client: Paired key encryption Server->>Client: Paired key encryption
Client->>Server: 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 #### 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 ### The encryption layer

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -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
}
}

Binary file not shown.

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NFiles</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@files@</string>
<key>files</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d file</string>
<key>other</key>
<string>%d files</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,264 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ShareViewController" customModule="ShareExtension" customModuleProvider="target">
<connections>
<outlet property="contentWrap" destination="ACO-U2-AZj" id="PAE-Pw-Vyx"/>
<outlet property="filesIcon" destination="afM-Om-nLQ" id="I58-a3-SBZ"/>
<outlet property="filesLabel" destination="OEz-QK-nem" id="his-3f-6kx"/>
<outlet property="largeProgress" destination="fCj-wF-rEj" id="jEg-Uc-Ur7"/>
<outlet property="listView" destination="gZI-Zb-Blx" id="VOC-3j-Pwy"/>
<outlet property="listViewWrapper" destination="A4d-Qt-PsT" id="8g5-VW-MIb"/>
<outlet property="loadingOverlay" destination="fLZ-IA-bPS" id="84g-Ef-GzW"/>
<outlet property="progressDeviceIcon" destination="84U-lB-SRB" id="LEL-BY-2FY"/>
<outlet property="progressDeviceIconWrap" destination="jBg-Ih-Gac" id="69x-6y-z0c"/>
<outlet property="progressDeviceName" destination="dJc-gw-4ux" id="ZE4-Kx-5hv"/>
<outlet property="progressDeviceSecondaryIcon" destination="BEO-kU-a2Q" id="iks-QR-2Od"/>
<outlet property="progressProgressBar" destination="J5x-hu-Kn5" id="vYH-DN-03b"/>
<outlet property="progressState" destination="y8I-D3-scQ" id="Tj3-xa-8VG"/>
<outlet property="progressView" destination="Q9K-dc-THx" id="LlE-4d-mI5"/>
<outlet property="view" destination="1" id="2"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1">
<rect key="frame" x="0.0" y="0.0" width="404" height="248"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NVE-vN-dkz">
<rect key="frame" x="325" y="3" width="76" height="32"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="60" id="cP1-hK-9ZX"/>
</constraints>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Up-t3-mwm">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="Qav-AK-DGt"/>
</connections>
</button>
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1xF-vr-5sH">
<rect key="frame" x="160" y="222" width="84" height="16"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4M6-D5-WIf">
<rect key="frame" x="0.0" y="0.0" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="BOe-aZ-Njc"/>
<constraint firstAttribute="height" constant="16" id="zLg-1a-wlZ"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NearDropIcon" id="q3u-Am-ZIA"/>
</imageView>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aNc-0i-CWK">
<rect key="frame" x="19" y="0.0" width="67" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="NearDrop" allowsEditingTextAttributes="YES" id="0xp-rC-2gr">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="afM-Om-nLQ">
<rect key="frame" x="10" y="196" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="3NE-mW-Jk0"/>
<constraint firstAttribute="height" constant="16" id="koV-ob-gSn"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="jOq-9r-V1W"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OEz-QK-nem">
<rect key="frame" x="29" y="196" width="367" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingMiddle" truncatesLastVisibleLine="YES" id="opy-Bj-x6u">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="ACO-U2-AZj">
<rect key="frame" x="0.0" y="40" width="404" height="146"/>
</customView>
</subviews>
<constraints>
<constraint firstItem="OEz-QK-nem" firstAttribute="top" secondItem="1xF-vr-5sH" secondAttribute="bottom" constant="10" id="852-oh-vM0"/>
<constraint firstItem="OEz-QK-nem" firstAttribute="leading" secondItem="afM-Om-nLQ" secondAttribute="trailing" constant="5" id="CIj-Dk-Md0"/>
<constraint firstAttribute="trailing" secondItem="ACO-U2-AZj" secondAttribute="trailing" id="HIg-sX-IK0"/>
<constraint firstItem="ACO-U2-AZj" firstAttribute="leading" secondItem="1" secondAttribute="leading" id="IGL-2g-wpe"/>
<constraint firstItem="afM-Om-nLQ" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="10" id="LK2-cg-qcY"/>
<constraint firstItem="NVE-vN-dkz" firstAttribute="top" secondItem="ACO-U2-AZj" secondAttribute="bottom" constant="10" id="RSD-PX-W0H"/>
<constraint firstAttribute="bottom" secondItem="NVE-vN-dkz" secondAttribute="bottom" constant="10" id="USG-Gg-of3"/>
<constraint firstAttribute="trailing" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="10" id="Z6U-4n-4tO"/>
<constraint firstItem="afM-Om-nLQ" firstAttribute="top" secondItem="1" secondAttribute="top" constant="36" id="fUG-kT-wb0"/>
<constraint firstItem="1xF-vr-5sH" firstAttribute="centerX" secondItem="1" secondAttribute="centerX" id="sCA-yf-S9L"/>
<constraint firstItem="1xF-vr-5sH" firstAttribute="top" secondItem="1" secondAttribute="top" constant="10" id="tFn-vk-9Cq"/>
<constraint firstAttribute="trailing" secondItem="OEz-QK-nem" secondAttribute="trailing" constant="10" id="vyd-BI-oDH"/>
<constraint firstItem="ACO-U2-AZj" firstAttribute="top" secondItem="OEz-QK-nem" secondAttribute="bottom" constant="10" id="x5r-wc-s4x"/>
</constraints>
<point key="canvasLocation" x="140" y="-36"/>
</customView>
<stackView wantsLayer="YES" distribution="equalSpacing" orientation="vertical" alignment="centerX" spacing="0.0" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="fLZ-IA-bPS">
<rect key="frame" x="0.0" y="0.0" width="521" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<progressIndicator maxValue="100" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fCj-wF-rEj">
<rect key="frame" x="245" y="68" width="32" height="32"/>
</progressIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="27P-yf-gUd">
<rect key="frame" x="189" y="42" width="144" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Looking for devices..." id="NaJ-Wx-Pim">
<font key="font" textStyle="headline" name=".SFNS-Bold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="V5W-Rm-YVM">
<rect key="frame" x="-2" y="0.0" width="525" height="32"/>
<textFieldCell key="cell" alignment="center" title="If you don't see your device, open &quot;Files by Google&quot; app and tap &quot;Receive&quot; on the Nearby Share tab." id="vla-gF-eJo">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="27P-yf-gUd" firstAttribute="top" secondItem="fCj-wF-rEj" secondAttribute="bottom" constant="10" id="87W-ec-fMh"/>
<constraint firstItem="V5W-Rm-YVM" firstAttribute="top" secondItem="27P-yf-gUd" secondAttribute="bottom" constant="10" id="OSI-wI-LcY"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
<point key="canvasLocation" x="660.5" y="-1.5"/>
</stackView>
<stackView wantsLayer="YES" distribution="equalSpacing" orientation="vertical" alignment="centerX" spacing="1" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="Q9K-dc-THx">
<rect key="frame" x="0.0" y="0.0" width="384" height="130"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="jBg-Ih-Gac">
<rect key="frame" x="168" y="82" width="48" height="48"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="84U-lB-SRB">
<rect key="frame" x="0.0" y="0.0" width="48" height="48"/>
<constraints>
<constraint firstAttribute="width" constant="48" id="9ld-k4-EkL"/>
<constraint firstAttribute="height" constant="48" id="XA4-2k-hqR"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="rb3-78-PXe"/>
</imageView>
<imageView hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="BEO-kU-a2Q">
<rect key="frame" x="24" y="-8" width="32" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="05I-Ef-84Z"/>
<constraint firstAttribute="width" constant="32" id="V0g-Fz-HWH"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSCaution" id="k1c-Xl-fTQ"/>
</imageView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="BEO-kU-a2Q" secondAttribute="trailing" constant="-8" id="59U-Zi-ESa"/>
<constraint firstAttribute="bottom" secondItem="BEO-kU-a2Q" secondAttribute="bottom" constant="-8" id="8ty-pq-miR"/>
<constraint firstItem="84U-lB-SRB" firstAttribute="top" secondItem="jBg-Ih-Gac" secondAttribute="top" id="MB9-UI-2mb"/>
<constraint firstItem="84U-lB-SRB" firstAttribute="leading" secondItem="jBg-Ih-Gac" secondAttribute="leading" id="b23-hJ-aWT"/>
<constraint firstAttribute="trailing" secondItem="84U-lB-SRB" secondAttribute="trailing" id="eKl-3V-K49"/>
<constraint firstAttribute="bottom" secondItem="84U-lB-SRB" secondAttribute="bottom" id="uh8-T1-Vd8"/>
</constraints>
</customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dJc-gw-4ux">
<rect key="frame" x="-2" y="56" width="388" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" id="QzI-qQ-OBb">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<progressIndicator maxValue="100" doubleValue="50" indeterminate="YES" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="J5x-hu-Kn5">
<rect key="frame" x="92" y="25" width="200" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="bM2-Ih-2UC"/>
</constraints>
</progressIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="y8I-D3-scQ">
<rect key="frame" x="165" y="0.0" width="54" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" id="Iin-UY-IE5">
<font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="y8I-D3-scQ" firstAttribute="top" secondItem="J5x-hu-Kn5" secondAttribute="bottom" constant="10" id="4EB-xc-Qni"/>
<constraint firstItem="dJc-gw-4ux" firstAttribute="leading" secondItem="Q9K-dc-THx" secondAttribute="leading" id="IXP-am-A8h"/>
<constraint firstItem="J5x-hu-Kn5" firstAttribute="top" secondItem="dJc-gw-4ux" secondAttribute="bottom" constant="10" id="Zav-Ho-QEJ"/>
<constraint firstItem="dJc-gw-4ux" firstAttribute="top" secondItem="jBg-Ih-Gac" secondAttribute="bottom" constant="10" id="bIE-Es-ZbK"/>
<constraint firstAttribute="trailing" secondItem="dJc-gw-4ux" secondAttribute="trailing" id="bKd-Vz-YA1"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
<point key="canvasLocation" x="4" y="-288"/>
</stackView>
<scrollView wantsLayer="YES" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="A4d-Qt-PsT">
<rect key="frame" x="0.0" y="0.0" width="271" height="172"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" id="cCr-8P-Lya">
<rect key="frame" x="1" y="1" width="269" height="170"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView id="gZI-Zb-Blx">
<rect key="frame" x="0.0" y="0.0" width="269" height="170"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumInteritemSpacing="10" minimumLineSpacing="10" id="ATS-Jd-odC">
<size key="itemSize" width="50" height="50"/>
</collectionViewFlowLayout>
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</collectionView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="3j2-z3-G1e">
<rect key="frame" x="-100" y="-100" width="402" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="wty-KV-JAg">
<rect key="frame" x="380" y="1" width="16" height="142"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<point key="canvasLocation" x="-397.5" y="30"/>
</scrollView>
</objects>
<resources>
<image name="NSCaution" width="32" height="32"/>
<image name="NearDropIcon" width="16" height="16"/>
</resources>
</document>

View File

@@ -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()
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="DeviceListCell" customModule="ShareExtension" customModuleProvider="target">
<connections>
<outlet property="view" destination="Hz6-mo-xeY" id="gtq-ca-SBh"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView misplaced="YES" id="Hz6-mo-xeY" customClass="NSButton">
<rect key="frame" x="0.0" y="0.0" width="206" height="82"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QPM-sE-maP">
<rect key="frame" x="-2" y="0.0" width="210" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="TBS-IE-2DT"/>
</constraints>
<textFieldCell key="cell" truncatesLastVisibleLine="YES" alignment="center" title="Label" id="cXi-mf-qP0">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="O1J-am-CoK">
<rect key="frame" x="79" y="42" width="48" height="48"/>
<constraints>
<constraint firstAttribute="width" constant="48" id="KYr-2E-wCO"/>
<constraint firstAttribute="height" constant="48" id="nik-I6-YgB"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="QGU-rR-rJQ"/>
</imageView>
</subviews>
<constraints>
<constraint firstItem="O1J-am-CoK" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="R76-2L-pXb"/>
<constraint firstAttribute="bottom" secondItem="QPM-sE-maP" secondAttribute="bottom" id="Ryn-nY-yZp"/>
<constraint firstItem="O1J-am-CoK" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="ZFA-S9-fpT"/>
<constraint firstAttribute="trailing" secondItem="QPM-sE-maP" secondAttribute="trailing" id="eOl-Va-HXq"/>
<constraint firstItem="QPM-sE-maP" firstAttribute="top" secondItem="O1J-am-CoK" secondAttribute="bottom" constant="10" id="leg-nD-mHB"/>
<constraint firstItem="QPM-sE-maP" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="vvH-Hq-iUk"/>
</constraints>
<connections>
<action selector="onClick:" target="-2" id="mve-gp-q4i"/>
</connections>
<point key="canvasLocation" x="-60" y="150"/>
</customView>
<collectionViewItem id="1Lb-iJ-Nar" customClass="DeviceListCell" customModule="ShareExtension" customModuleProvider="target">
<connections>
<outlet property="imageView" destination="O1J-am-CoK" id="zQn-mV-bRy"/>
<outlet property="textField" destination="QPM-sE-maP" id="s0j-Qq-7iA"/>
<outlet property="view" destination="Hz6-mo-xeY" id="gZn-20-o77"/>
</connections>
</collectionViewItem>
</objects>
</document>

27
ShareExtension/Info.plist Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>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 &gt; 0</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -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<ObjCBool>.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
}
}

Binary file not shown.

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NFiles</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@files@</string>
<key>files</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d файл</string>
<key>few</key>
<string>%d файла</string>
<key>many</key>
<string>%d файлов</string>
<key>other</key>
<string>%d файлов</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -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\" и нажмите \"Получить\" на вкладке Обмен.";