Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d214aa5eb | ||
|
|
27d975b1f0 | ||
|
|
173eb741fa | ||
|
|
17410e2c00 | ||
|
|
3ef4280df1 | ||
|
|
1b66d15cad | ||
|
|
e8dcf61daa | ||
|
|
7d72fa1ac1 | ||
|
|
6eb2822632 | ||
|
|
fe1e286182 | ||
|
|
4e0f37d129 | ||
|
|
6b3196e798 | ||
|
|
9829d6ceb4 | ||
|
|
6c4895d68f | ||
|
|
33716d7f9d | ||
|
|
e27051e71e | ||
|
|
ed2c80b8c7 | ||
|
|
62bbee528e | ||
|
|
00e3b5ad14 | ||
|
|
3d593a006c | ||
|
|
b65a6e6be0 | ||
|
|
190d9f35dd | ||
|
|
ebfe7922ec | ||
|
|
005d642dd8 | ||
|
|
c349ffde7f | ||
|
|
f582d86455 | ||
|
|
e55a0959af | ||
|
|
278fe4e30d |
13
.github/workflows/build-app.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- OpenHaystack/**
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
APP: OpenHaystack
|
||||
@@ -18,7 +19,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
format-swift:
|
||||
runs-on: macos-11
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
@@ -28,7 +29,7 @@ jobs:
|
||||
run: swift-format lint --recursive .
|
||||
|
||||
format-objc:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
@@ -38,16 +39,16 @@ jobs:
|
||||
run: clang-format -n **/*.{h,m}
|
||||
|
||||
build-app:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
needs:
|
||||
- format-swift
|
||||
- format-objc
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
- name: "Select Xcode 12"
|
||||
uses: devbotsxyz/xcode-select@v1
|
||||
- name: "Select Xcode 15.3"
|
||||
uses: keehun/xcode-select@v1
|
||||
with:
|
||||
version: "12"
|
||||
version: "15.3"
|
||||
- name: "Archive project"
|
||||
run: xcodebuild archive -scheme ${APP} -configuration release -archivePath ${APP}.xcarchive
|
||||
|
||||
4
.github/workflows/build-cve-2020-9986.yaml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
- name: "Select Xcode 12"
|
||||
uses: devbotsxyz/xcode-select@v1
|
||||
uses: keehun/xcode-select@v1
|
||||
with:
|
||||
version: "12"
|
||||
- name: "Archive project"
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
- name: "Select Xcode 12"
|
||||
uses: devbotsxyz/xcode-select@v1
|
||||
uses: keehun/xcode-select@v1
|
||||
with:
|
||||
version: "12"
|
||||
- name: "Archive project"
|
||||
|
||||
1
.github/workflows/build-firmware-esp32.yaml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/ESP32/**
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-firmware-esp32:
|
||||
|
||||
3
.github/workflows/build-firmware.yaml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/Microbit_v1/**
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -16,7 +17,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
build-firmware:
|
||||
runs-on: macos-11
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
9
.github/workflows/release.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-firmware-esp32:
|
||||
@@ -30,7 +31,7 @@ jobs:
|
||||
|
||||
build-and-release:
|
||||
name: "Create release on GitHub"
|
||||
runs-on: macos-11
|
||||
runs-on: macos-14
|
||||
env:
|
||||
APP: OpenHaystack
|
||||
PROJECT_DIR: OpenHaystack
|
||||
@@ -42,10 +43,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: "Select Xcode 12"
|
||||
uses: devbotsxyz/xcode-select@v1
|
||||
- name: "Select Xcode 15.3"
|
||||
uses: keehun/xcode-select@v1
|
||||
with:
|
||||
version: "12"
|
||||
version: "15.3"
|
||||
- name: "Add ESP32 firmware"
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
|
||||
6
.gitignore
vendored
@@ -2,6 +2,10 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/xcode,swift
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift
|
||||
|
||||
## macOS ##
|
||||
|
||||
.DS_Store
|
||||
|
||||
### Swift ###
|
||||
# Xcode
|
||||
#
|
||||
@@ -106,4 +110,4 @@ iOSInjectionProject/
|
||||
# End of https://www.toptal.com/developers/gitignore/api/xcode,swift
|
||||
|
||||
# Exports folder
|
||||
Exports/
|
||||
Exports/
|
||||
31
CITATION.cff
Normal file
@@ -0,0 +1,31 @@
|
||||
# This CITATION.cff file was generated with cffinit.
|
||||
# Visit https://bit.ly/cffinit to generate yours today!
|
||||
|
||||
cff-version: 1.2.0
|
||||
title: OpenHaystack
|
||||
message: 'If you use this software, please cite it as below.'
|
||||
type: software
|
||||
authors:
|
||||
- given-names: Alexander
|
||||
family-names: Heinrich
|
||||
affiliation: 'SEEMOO, TU Darmstadt'
|
||||
orcid: 'https://orcid.org/0000-0002-1150-1922'
|
||||
- given-names: Milan
|
||||
family-names: Stute
|
||||
affiliation: 'SEEMOO, TU Darmstadt'
|
||||
orcid: 'https://orcid.org/0000-0003-4921-8476'
|
||||
- given-names: Matthias
|
||||
family-names: Hollick
|
||||
affiliation: 'SEEMOO, TU Darmstadt'
|
||||
orcid: 'https://orcid.org/0000-0002-9163-5989'
|
||||
repository-code: 'https://github.com/seemoo-lab/openhaystack'
|
||||
abstract: >-
|
||||
OpenHaystack is a framework for tracking personal
|
||||
Bluetooth devices via Apple's massive Find My network. Use
|
||||
it to create your own tracking tags that you can append to
|
||||
physical objects (keyrings, backpacks, ...) or integrate
|
||||
it into other Bluetooth-capable devices such as notebooks.
|
||||
license: AGPL-3.0
|
||||
commit: 7d72fa1ac19d2a9f6dec43011be07df8976a8b02
|
||||
version: 0.5.3
|
||||
date-released: '2023-10-09'
|
||||
@@ -3,10 +3,14 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5A2C9089273425720044407E /* NRF in Resources */ = {isa = PBXBuildFile; fileRef = 5A2C9088273425720044407E /* NRF */; };
|
||||
5A2C908B2734266A0044407E /* DataToHexExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2C908A2734266A0044407E /* DataToHexExtension.swift */; };
|
||||
5A2C908D273429360044407E /* NRFController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2C908C273429360044407E /* NRFController.swift */; };
|
||||
5A2C908F273429540044407E /* NRFInstallSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2C908E273429540044407E /* NRFInstallSheet.swift */; };
|
||||
78014A2925DC08580089F6D9 /* MicrobitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78014A2725DC01220089F6D9 /* MicrobitController.swift */; };
|
||||
78014A2B25DC22120089F6D9 /* sample.bin in Resources */ = {isa = PBXBuildFile; fileRef = 78014A2A25DC22110089F6D9 /* sample.bin */; };
|
||||
78014A2F25DC2F100089F6D9 /* pattern_sample.bin in Resources */ = {isa = PBXBuildFile; fileRef = 78014A2E25DC2F100089F6D9 /* pattern_sample.bin */; };
|
||||
@@ -29,6 +33,8 @@
|
||||
781EB43125DADF2B00FEAA19 /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 781EB40F25DADB0600FEAA19 /* AnisetteDataManager.swift */; };
|
||||
7821DAD125F7B2C10054DC33 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7821DAD025F7B2C10054DC33 /* FileManager.swift */; };
|
||||
7821DAD325F7C39A0054DC33 /* ESP32InstallSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */; };
|
||||
782853C22755103A00B18EDE /* UpdateCheckController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782853C12755103A00B18EDE /* UpdateCheckController.swift */; };
|
||||
782853C427551B4400B18EDE /* UpdateCheckTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782853C327551B4400B18EDE /* UpdateCheckTests.swift */; };
|
||||
78286CB225E3ACE700F65511 /* OpenHaystackPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286CAF25E3ACE700F65511 /* OpenHaystackPluginService.m */; };
|
||||
78286D1F25E3D8B800F65511 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286CB025E3ACE700F65511 /* ALTAnisetteData.m */; };
|
||||
78286D2A25E3EC3200F65511 /* AppleAccountData.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286D2925E3EC3200F65511 /* AppleAccountData.m */; };
|
||||
@@ -52,6 +58,7 @@
|
||||
78EC227225DBC8CE0042B775 /* Accessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227125DBC8CE0042B775 /* Accessory.swift */; };
|
||||
78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227625DBDB7E0042B775 /* KeychainController.swift */; };
|
||||
78F8BB4C261C50EB00D9F37F /* LargeButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78F8BB4B261C50EB00D9F37F /* LargeButtonStyle.swift */; };
|
||||
9ED440A02C1605EF002574D1 /* OpenHaystackSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */; };
|
||||
F126102F2600D1D80066A859 /* Slider+LogScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = F126102E2600D1D80066A859 /* Slider+LogScale.swift */; };
|
||||
F12D5A5A25FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */; };
|
||||
F12D5A6025FA79FA00CBBA09 /* Advertisement.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */; };
|
||||
@@ -108,6 +115,10 @@
|
||||
025DFEDB248FED250039C718 /* DecryptReports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptReports.swift; sourceTree = "<group>"; };
|
||||
0298C0C8248F9506003928FE /* AuthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthKit.framework; path = ../../../../../../../../../../System/Library/PrivateFrameworks/AuthKit.framework; sourceTree = "<group>"; };
|
||||
116B4EEC24A913AA007BA636 /* SavePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavePanel.swift; sourceTree = "<group>"; };
|
||||
5A2C9088273425720044407E /* NRF */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NRF; sourceTree = "<group>"; };
|
||||
5A2C908A2734266A0044407E /* DataToHexExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataToHexExtension.swift; sourceTree = "<group>"; };
|
||||
5A2C908C273429360044407E /* NRFController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRFController.swift; sourceTree = "<group>"; };
|
||||
5A2C908E273429540044407E /* NRFInstallSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRFInstallSheet.swift; sourceTree = "<group>"; };
|
||||
78014A2725DC01220089F6D9 /* MicrobitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrobitController.swift; sourceTree = "<group>"; };
|
||||
78014A2A25DC22110089F6D9 /* sample.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = sample.bin; sourceTree = "<group>"; };
|
||||
78014A2E25DC2F100089F6D9 /* pattern_sample.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = pattern_sample.bin; sourceTree = "<group>"; };
|
||||
@@ -127,6 +138,8 @@
|
||||
781EB40F25DADB0600FEAA19 /* AnisetteDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteDataManager.swift; sourceTree = "<group>"; };
|
||||
7821DAD025F7B2C10054DC33 /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = "<group>"; };
|
||||
7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ESP32InstallSheet.swift; sourceTree = "<group>"; };
|
||||
782853C12755103A00B18EDE /* UpdateCheckController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCheckController.swift; sourceTree = "<group>"; };
|
||||
782853C327551B4400B18EDE /* UpdateCheckTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCheckTests.swift; sourceTree = "<group>"; };
|
||||
78286C8E25E3AC0400F65511 /* OpenHaystackMail.mailbundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenHaystackMail.mailbundle; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
78286C9025E3AC0400F65511 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
78286CAE25E3ACE700F65511 /* OpenHaystackPluginService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenHaystackPluginService.h; sourceTree = "<group>"; };
|
||||
@@ -157,6 +170,7 @@
|
||||
78EC227125DBC8CE0042B775 /* Accessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessory.swift; sourceTree = "<group>"; };
|
||||
78EC227625DBDB7E0042B775 /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = "<group>"; };
|
||||
78F8BB4B261C50EB00D9F37F /* LargeButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeButtonStyle.swift; sourceTree = "<group>"; };
|
||||
9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHaystackSettingsView.swift; sourceTree = "<group>"; };
|
||||
F126102E2600D1D80066A859 /* Slider+LogScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Slider+LogScale.swift"; sourceTree = "<group>"; };
|
||||
F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothAccessoryScanner.swift; sourceTree = "<group>"; };
|
||||
F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advertisement.swift; sourceTree = "<group>"; };
|
||||
@@ -203,6 +217,7 @@
|
||||
78023CAC25F7775300B083EF /* Firmwares */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5A2C9088273425720044407E /* NRF */,
|
||||
78023CAE25F7797400B083EF /* ESP32 */,
|
||||
78023CAD25F7775A00B083EF /* Microbit */,
|
||||
);
|
||||
@@ -328,6 +343,7 @@
|
||||
78EC226525DAE0BE0042B775 /* Info.plist */,
|
||||
78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */,
|
||||
F1647C1525FF6C61004144D6 /* BluetoothTests.swift */,
|
||||
782853C327551B4400B18EDE /* UpdateCheckTests.swift */,
|
||||
);
|
||||
path = OpenHaystackTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -346,6 +362,9 @@
|
||||
78023CAA25F7767000B083EF /* ESP32Controller.swift */,
|
||||
7821DAD025F7B2C10054DC33 /* FileManager.swift */,
|
||||
F1647C1A25FF7954004144D6 /* AccessoryNearbyMonitor.swift */,
|
||||
5A2C908A2734266A0044407E /* DataToHexExtension.swift */,
|
||||
5A2C908C273429360044407E /* NRFController.swift */,
|
||||
782853C12755103A00B18EDE /* UpdateCheckController.swift */,
|
||||
);
|
||||
path = HaystackApp;
|
||||
sourceTree = "<group>";
|
||||
@@ -363,6 +382,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78F8BB4A261C50D500D9F37F /* Styles */,
|
||||
9ED4409F2C1605EF002574D1 /* OpenHaystackSettingsView.swift */,
|
||||
78286D7625E5114600F65511 /* ActivityIndicator.swift */,
|
||||
78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */,
|
||||
78486BEE25DD711E0007ED87 /* PopUpAlertView.swift */,
|
||||
@@ -373,6 +393,7 @@
|
||||
7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */,
|
||||
78D9B80525F7CF60009B9CE8 /* ManageAccessoriesView.swift */,
|
||||
F126102E2600D1D80066A859 /* Slider+LogScale.swift */,
|
||||
5A2C908E273429540044407E /* NRFInstallSheet.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -510,6 +531,7 @@
|
||||
78023CAF25F7797400B083EF /* ESP32 in Resources */,
|
||||
7899D1D625DE74EE00115740 /* firmware.bin in Resources */,
|
||||
781EB3FE25DAD7EA00FEAA19 /* MapViewController.xib in Resources */,
|
||||
5A2C9089273425720044407E /* NRF in Resources */,
|
||||
781EB40025DAD7EA00FEAA19 /* Preview Assets.xcassets in Resources */,
|
||||
781EB40225DAD7EA00FEAA19 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
@@ -597,6 +619,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5A2C908D273429360044407E /* NRFController.swift in Sources */,
|
||||
781EB43125DADF2B00FEAA19 /* AnisetteDataManager.swift in Sources */,
|
||||
7851F1DD25EE90FA0049480D /* AccessoryMapView.swift in Sources */,
|
||||
7899D1E925DEBF4900115740 /* AccessoryMapAnnotation.swift in Sources */,
|
||||
@@ -605,6 +628,7 @@
|
||||
78286D8C25E5355B00F65511 /* PreviewData.swift in Sources */,
|
||||
781EB3EB25DAD7EA00FEAA19 /* SavePanel.swift in Sources */,
|
||||
7899D1E125DE97E200115740 /* IconSelectionView.swift in Sources */,
|
||||
5A2C908F273429540044407E /* NRFInstallSheet.swift in Sources */,
|
||||
78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */,
|
||||
78D9B80625F7CF60009B9CE8 /* ManageAccessoriesView.swift in Sources */,
|
||||
78486BEF25DD711E0007ED87 /* PopUpAlertView.swift in Sources */,
|
||||
@@ -614,6 +638,7 @@
|
||||
78286D1F25E3D8B800F65511 /* ALTAnisetteData.m in Sources */,
|
||||
781EB3EC25DAD7EA00FEAA19 /* DecryptReports.swift in Sources */,
|
||||
78EC226C25DBC2E40042B775 /* OpenHaystackMainView.swift in Sources */,
|
||||
5A2C908B2734266A0044407E /* DataToHexExtension.swift in Sources */,
|
||||
78EC227225DBC8CE0042B775 /* Accessory.swift in Sources */,
|
||||
7821DAD125F7B2C10054DC33 /* FileManager.swift in Sources */,
|
||||
78286E0225E66F9400F65511 /* AccessoryListEntry.swift in Sources */,
|
||||
@@ -622,6 +647,7 @@
|
||||
7821DAD325F7C39A0054DC33 /* ESP32InstallSheet.swift in Sources */,
|
||||
781EB3F125DAD7EA00FEAA19 /* FindMyKeyDecoder.swift in Sources */,
|
||||
787D8AC125DECD3C00148766 /* AccessoryController.swift in Sources */,
|
||||
9ED440A02C1605EF002574D1 /* OpenHaystackSettingsView.swift in Sources */,
|
||||
78023CAB25F7767000B083EF /* ESP32Controller.swift in Sources */,
|
||||
F12D5A6025FA79FA00CBBA09 /* Advertisement.swift in Sources */,
|
||||
781EB3F225DAD7EA00FEAA19 /* OpenHaystackApp.swift in Sources */,
|
||||
@@ -629,6 +655,7 @@
|
||||
78F8BB4C261C50EB00D9F37F /* LargeButtonStyle.swift in Sources */,
|
||||
781EB3F425DAD7EA00FEAA19 /* FindMyController.swift in Sources */,
|
||||
781EB3F525DAD7EA00FEAA19 /* BoringSSL.m in Sources */,
|
||||
782853C22755103A00B18EDE /* UpdateCheckController.swift in Sources */,
|
||||
F12D5A5A25FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift in Sources */,
|
||||
78286D5625E401F000F65511 /* MailPluginManager.swift in Sources */,
|
||||
);
|
||||
@@ -648,6 +675,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78023CB125F7841F00B083EF /* MicrocontrollerTests.swift in Sources */,
|
||||
782853C427551B4400B18EDE /* UpdateCheckTests.swift in Sources */,
|
||||
F1647C1625FF6C61004144D6 /* BluetoothTests.swift in Sources */,
|
||||
78EC226425DAE0BE0042B775 /* OpenHaystackTests.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -1,34 +1,60 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "swift-crypto",
|
||||
"repositoryURL": "https://github.com/apple/swift-crypto.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "3bea268b223651c4ab7b7b9ad62ef9b2d4143eb6",
|
||||
"version": "1.1.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-nio",
|
||||
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "6aa9347d9bc5bbfe6a84983aec955c17ffea96ef",
|
||||
"version": "2.33.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-nio-ssl",
|
||||
"repositoryURL": "https://github.com/apple/swift-nio-ssl",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "5e68c1ded15619bb281b273fa8c2d8fd7f7b2b7d",
|
||||
"version": "2.16.1"
|
||||
}
|
||||
"originHash" : "bfeb00ee66eb6db71ff8535b5ea7585725e9fe73d97f066170be55b745d346e9",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-atomics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-atomics.git",
|
||||
"state" : {
|
||||
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections.git",
|
||||
"state" : {
|
||||
"revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-crypto",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-crypto.git",
|
||||
"state" : {
|
||||
"revision" : "ddb07e896a2a8af79512543b1c7eb9797f8898a5",
|
||||
"version" : "1.1.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio.git",
|
||||
"state" : {
|
||||
"revision" : "9428f62793696d9a0cc1f26a63f63bb31da0516d",
|
||||
"version" : "2.66.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-ssl",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-ssl",
|
||||
"state" : {
|
||||
"revision" : "2b09805797f21c380f7dc9bedaab3157c5508efb",
|
||||
"version" : "2.27.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-system",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-system.git",
|
||||
"state" : {
|
||||
"revision" : "f9266c85189c2751589a50ea5aec72799797e471",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
|
||||
@@ -74,6 +74,12 @@
|
||||
ReferencedContainer = "container:OpenHaystack.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-stopUpdateCheck"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -10,6 +10,20 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
/// Uses AOSKit to get anisette headers
|
||||
@objc private protocol AOSUtilitiesProtocol
|
||||
{
|
||||
static var machineSerialNumber: String? { get }
|
||||
static var machineUDID: String? { get }
|
||||
|
||||
static func retrieveOTPHeadersForDSID(_ dsid: String) -> [String: Any]?
|
||||
|
||||
// Non-static versions used for respondsToSelector:
|
||||
var machineSerialNumber: String? { get }
|
||||
var machineUDID: String? { get }
|
||||
func retrieveOTPHeadersForDSID(_ dsid: String) -> [String: Any]?
|
||||
}
|
||||
|
||||
/// Uses the AltStore Mail plugin to access recent anisette data.
|
||||
public class AnisetteDataManager: NSObject {
|
||||
@objc static let shared = AnisetteDataManager()
|
||||
@@ -28,7 +42,7 @@ public class AnisetteDataManager: NSObject {
|
||||
}
|
||||
|
||||
func requestAnisetteData(_ completion: @escaping (Result<AppleAccountData, Error>) -> Void) {
|
||||
if let accountData = self.requestAnisetteDataAuthKit() {
|
||||
if let accountData = self.requestAnisetteDataAOSKit() {
|
||||
os_log(.debug, "Anisette Data loaded %@", accountData.debugDescription)
|
||||
completion(.success(accountData))
|
||||
return
|
||||
@@ -86,6 +100,61 @@ public class AnisetteDataManager: NSObject {
|
||||
return accountData
|
||||
}
|
||||
|
||||
/// Adapted from: https://github.com/altstoreio/AltStore/blob/main/AltServer/Anisette%20Data/AnisetteDataManager.swift
|
||||
func requestAnisetteDataAOSKit() -> AppleAccountData? {
|
||||
do
|
||||
{
|
||||
let aosKitURL = URL(fileURLWithPath: "/System/Library/PrivateFrameworks/AOSKit.framework")
|
||||
guard let aosKit = Bundle(url: aosKitURL) else { throw AnisetteDataError.aosKitFailure }
|
||||
try aosKit.loadAndReturnError()
|
||||
|
||||
guard let AOSUtilitiesClass = NSClassFromString("AOSUtilities"),
|
||||
AOSUtilitiesClass.responds(to: #selector(AOSUtilitiesProtocol.retrieveOTPHeadersForDSID(_:))),
|
||||
AOSUtilitiesClass.responds(to: #selector(getter: AOSUtilitiesProtocol.machineSerialNumber)),
|
||||
AOSUtilitiesClass.responds(to: #selector(getter: AOSUtilitiesProtocol.machineUDID))
|
||||
else { throw AnisetteDataError.aosKitFailure }
|
||||
|
||||
let AOSUtilities = unsafeBitCast(AOSUtilitiesClass, to: AOSUtilitiesProtocol.Type.self)
|
||||
|
||||
guard let anisetteData = AOSUtilities.retrieveOTPHeadersForDSID("-2") else { throw AnisetteDataError.aosKitFailure }
|
||||
|
||||
guard let machineID = anisetteData["X-Apple-MD-M"] as? String,
|
||||
let otp = anisetteData["X-Apple-MD"] as? String,
|
||||
let deviceId = AOSUtilities.machineUDID,
|
||||
let localUserId = deviceId.data(using: .utf8)?.base64EncodedString(),
|
||||
let deviceClass = NSClassFromString("AKDevice")
|
||||
else {
|
||||
print("Failure retrieving anisette headers from AOSKit")
|
||||
throw AnisetteDataError.aosKitFailure
|
||||
}
|
||||
let device: AKDevice = deviceClass.current()
|
||||
|
||||
let routingInfo: UInt64 = 84215040
|
||||
let accountData = AppleAccountData(
|
||||
machineID: machineID,
|
||||
oneTimePassword: otp,
|
||||
localUserID: localUserId,
|
||||
routingInfo: routingInfo,
|
||||
deviceUniqueIdentifier: device.uniqueDeviceIdentifier(),
|
||||
deviceSerialNumber: device.serialNumber(),
|
||||
deviceDescription: device.serverFriendlyDescription(),
|
||||
date: Date(),
|
||||
locale: Locale.current,
|
||||
timeZone: TimeZone.current)
|
||||
|
||||
/// This only works with SIP disabled
|
||||
if let spToken = ReportsFetcher().fetchSearchpartyToken() {
|
||||
accountData.searchPartyToken = spToken
|
||||
}
|
||||
return accountData
|
||||
}
|
||||
catch
|
||||
{
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func requestAnisetteDataObjc(_ completion: @escaping ([AnyHashable: Any]?) -> Void) {
|
||||
self.requestAnisetteData { result in
|
||||
switch result {
|
||||
@@ -98,7 +167,8 @@ public class AnisetteDataManager: NSObject {
|
||||
"X-Apple-I-MD-M": data.machineID,
|
||||
"X-Apple-I-MD": data.oneTimePassword,
|
||||
"X-Apple-I-TimeZone": String(data.timeZone.abbreviation() ?? "UTC"),
|
||||
"X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: data.date),
|
||||
// "X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: data.date),
|
||||
"X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: Date()),
|
||||
"X-Apple-I-MD-RINFO": String(data.routingInfo),
|
||||
] as [AnyHashable: Any])
|
||||
}
|
||||
@@ -162,4 +232,5 @@ extension AnisetteDataManager {
|
||||
enum AnisetteDataError: Error {
|
||||
case pluginNotFound
|
||||
case invalidAnisetteData
|
||||
case aosKitFailure
|
||||
}
|
||||
|
||||
@@ -21,9 +21,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// For OF the first byte has to be dropped
|
||||
+ (NSData *_Nullable)derivePublicKeyFromPrivateKey:(NSData *)privateKeyData;
|
||||
|
||||
/// Derive a public key from a given private key
|
||||
/// @param privateKeyData an EC private key on the P-224 curve
|
||||
/// @returns The public key in a uncompressed format using 28*2+1 bytes. The first byte is used for identifying if its odd or even.
|
||||
+ (NSData *_Nullable)deriveUncompressedPublicKeyFromPrivateKey:(NSData *)privateKeyData ;
|
||||
|
||||
/// Generate a new EC private key and exports it as data
|
||||
+ (NSData *_Nullable)generateNewPrivateKey;
|
||||
|
||||
/// Calculate private key from derived data
|
||||
+ (NSData *_Nullable)calculatePrivateKeyFromSharedData:(NSData *)sharedData masterBeaconPrivateKey:(NSData *)masterBeaconPrivateKey;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
char *buf;
|
||||
BIO_get_mem_data(bio, &buf);
|
||||
NSLog(@"Generating shared key failed %s", buf);
|
||||
free(buf);
|
||||
BIO_free(bio);
|
||||
}
|
||||
|
||||
@@ -145,6 +144,30 @@
|
||||
return publicKeyBytes;
|
||||
}
|
||||
|
||||
/// Derive a uncompressed public key from a given private key
|
||||
/// @param privateKeyData an EC private key on the P-224 curve
|
||||
+ (NSData *_Nullable)deriveUncompressedPublicKeyFromPrivateKey:(NSData *)privateKeyData {
|
||||
EC_GROUP *curve = EC_GROUP_new_by_curve_name(NID_secp224r1);
|
||||
EC_KEY *key = [self deriveEllipticCurvePrivateKey:privateKeyData group:curve];
|
||||
|
||||
const EC_POINT *publicKey = EC_KEY_get0_public_key(key);
|
||||
|
||||
size_t keySize = 28*2 + 1;
|
||||
NSMutableData *publicKeyBytes = [[NSMutableData alloc] initWithLength:keySize];
|
||||
|
||||
size_t size = EC_POINT_point2oct(curve, publicKey, POINT_CONVERSION_UNCOMPRESSED, publicKeyBytes.mutableBytes, keySize, NULL);
|
||||
|
||||
//Free
|
||||
EC_KEY_free(key);
|
||||
EC_GROUP_free(curve);
|
||||
|
||||
if (size == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return publicKeyBytes;
|
||||
}
|
||||
|
||||
+ (NSData *_Nullable)generateNewPrivateKey {
|
||||
EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp224r1);
|
||||
if (EC_KEY_generate_key_fips(key) == 0) {
|
||||
@@ -168,6 +191,142 @@
|
||||
return privateKeyBytes;
|
||||
}
|
||||
|
||||
+ (NSData *_Nullable)internalCalculatePrivateKeyFromSharedData:(NSData *)sharedData masterBeaconPrivateKey:(NSData *)masterBeaconPrivateKey
|
||||
curve:(EC_GROUP *) curve
|
||||
bignum_context:(BN_CTX *) context
|
||||
order:(BIGNUM *) order
|
||||
u_i_bn:(BIGNUM *) u_i_bn
|
||||
v_i_bn:(BIGNUM *) v_i_bn
|
||||
d_0_bn:(BIGNUM *) d_0_bn
|
||||
d_i_bn:(BIGNUM *) d_i_bn
|
||||
tmp_bn:(BIGNUM *) tmp_bn{
|
||||
// get (order of G) - 1 of our curve
|
||||
int res = EC_GROUP_get_order(curve, order, context);
|
||||
EC_GROUP_free(curve);
|
||||
if(res != 1){
|
||||
NSLog(@"Could not get Order of G for NID_secp224r1 with error: %d", res);
|
||||
return nil;
|
||||
}
|
||||
|
||||
res = BN_sub_word(order, 1);
|
||||
if(res != 1){
|
||||
NSLog(@"Could not calculate order - 1 (%d)", res);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// get u_i and v_i as BIGNUM
|
||||
NSData *u_i_data = [sharedData subdataWithRange:NSMakeRange(0, sharedData.length/2)];
|
||||
NSData *v_i_data = [sharedData subdataWithRange:NSMakeRange(sharedData.length/2, sharedData.length/2)];
|
||||
|
||||
/*
|
||||
NSLog(@"u_i_data: %@", u_i_data);
|
||||
NSLog(@"v_i_data: %@", v_i_data);
|
||||
*/
|
||||
|
||||
BN_bin2bn(u_i_data.bytes, u_i_data.length, u_i_bn);
|
||||
BN_bin2bn(v_i_data.bytes, v_i_data.length, v_i_bn);
|
||||
|
||||
//Calculate:
|
||||
//u_i = u_i (mod q-1) + 1
|
||||
res = BN_mod(tmp_bn, u_i_bn, order, context);
|
||||
if (res != 1){
|
||||
NSLog(@"Error while calculating u_i (mod q-1) (%d)", res);
|
||||
return nil;
|
||||
}
|
||||
BN_copy(u_i_bn, tmp_bn);
|
||||
res = BN_add_word(u_i_bn, 1);
|
||||
if (res != 1){
|
||||
NSLog(@"Error while adding 1 to v_i (mod q-1) (%d)", res);
|
||||
return nil;
|
||||
}
|
||||
//v_i = v_i (mod q-1) + 1
|
||||
res = BN_mod(tmp_bn, v_i_bn, order, context);
|
||||
if (res != 1){
|
||||
NSLog(@"Error while calculating u_i (mod q-1) (%d)", res);
|
||||
return nil;
|
||||
}
|
||||
BN_copy(v_i_bn, tmp_bn);
|
||||
res = BN_add_word(v_i_bn, 1);
|
||||
if (res != 1){
|
||||
NSLog(@"Error while adding 1 to v_i (mod q-1) (%d)", res);
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*
|
||||
size_t uv_size = BN_num_bytes(u_i_bn);
|
||||
NSMutableData *u_i_data2 = [[NSMutableData alloc] initWithLength:uv_size];
|
||||
BN_bn2bin(u_i_bn, u_i_data2.mutableBytes);
|
||||
NSLog(@"u_i_data: %@", u_i_data2);
|
||||
|
||||
uv_size = BN_num_bytes(u_i_bn);
|
||||
NSMutableData *v_i_data2 = [[NSMutableData alloc] initWithLength:uv_size];
|
||||
BN_bn2bin(v_i_bn, v_i_data2.mutableBytes);
|
||||
NSLog(@"v_i_data: %@", v_i_data2);
|
||||
*/
|
||||
|
||||
// calculate d_i = d_0_bn * u_i_bn + v_i_bn (new private key)
|
||||
BN_bin2bn(masterBeaconPrivateKey.bytes, masterBeaconPrivateKey.length, d_0_bn);
|
||||
res = BN_mul(tmp_bn, d_0_bn, u_i_bn, context);
|
||||
if (res != 1) {
|
||||
NSLog(@"Failed bignum multiplication with error: %d", res);
|
||||
return nil;
|
||||
}
|
||||
|
||||
res = BN_add(d_i_bn, tmp_bn, v_i_bn);
|
||||
if (res != 1) {
|
||||
NSLog(@"Failed bignum addition with error: %d", res);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// normalize point to 28 bytes to have a valid scaler as private key
|
||||
EC_GROUP_get_order(curve, order, context);
|
||||
BN_copy(tmp_bn, d_i_bn);
|
||||
res = BN_mod(d_i_bn, tmp_bn, order, context);
|
||||
if(res != 1){
|
||||
NSLog(@"Failed bignum modulo with error: %d", res);
|
||||
}
|
||||
|
||||
// get private key as bytes
|
||||
size_t d_i_size = BN_num_bytes(d_i_bn);
|
||||
NSMutableData *privateKeyBytes = [[NSMutableData alloc] initWithLength:d_i_size];
|
||||
size_t size = BN_bn2bin(d_i_bn, privateKeyBytes.mutableBytes);
|
||||
|
||||
if(size < 1){
|
||||
return nil;
|
||||
}
|
||||
|
||||
return privateKeyBytes;
|
||||
}
|
||||
|
||||
+ (NSData *_Nullable)calculatePrivateKeyFromSharedData:(NSData *)sharedData masterBeaconPrivateKey:(NSData *)masterBeaconPrivateKey {
|
||||
//Get the group
|
||||
EC_GROUP *curve = EC_GROUP_new_by_curve_name(NID_secp224r1);
|
||||
// Create big number context
|
||||
BN_CTX *ctx = BN_CTX_new();
|
||||
BN_CTX_start(ctx);
|
||||
|
||||
BIGNUM *order = BN_new();
|
||||
BIGNUM *u_i_bn = BN_new();
|
||||
BIGNUM *v_i_bn = BN_new();
|
||||
BIGNUM *d_0_bn = BN_new();
|
||||
BIGNUM *d_i_bn = BN_new();
|
||||
BIGNUM *tmp_bn = BN_new();
|
||||
|
||||
NSData* privateKeyBytes = [self internalCalculatePrivateKeyFromSharedData:sharedData masterBeaconPrivateKey:masterBeaconPrivateKey curve:curve bignum_context:ctx order:order u_i_bn:u_i_bn v_i_bn:v_i_bn d_0_bn:d_0_bn d_i_bn:d_i_bn tmp_bn:tmp_bn];
|
||||
|
||||
// Free all the things
|
||||
EC_GROUP_free(curve);
|
||||
BN_CTX_free(ctx);
|
||||
BN_free(order);
|
||||
BN_free(u_i_bn);
|
||||
BN_free(v_i_bn);
|
||||
BN_free(d_0_bn);
|
||||
BN_free(d_i_bn);
|
||||
BN_free(tmp_bn);
|
||||
|
||||
return privateKeyBytes;
|
||||
}
|
||||
|
||||
+ (void)printPoint:(const EC_POINT *)point withGroup:(EC_GROUP *)group {
|
||||
NSMutableData *pointData = [[NSMutableData alloc] initWithLength:256];
|
||||
|
||||
|
||||
@@ -20,7 +20,12 @@ struct DecryptReports {
|
||||
/// - Throws: Errors if the decryption fails
|
||||
/// - Returns: An decrypted location report
|
||||
static func decrypt(report: FindMyReport, with key: FindMyKey) throws -> FindMyLocationReport {
|
||||
let payloadData = report.payload
|
||||
var payloadData = report.payload
|
||||
/// Fix decryption for new report format
|
||||
/// See: https://github.com/biemster/FindMy/issues/52
|
||||
if payloadData.count > 88 {
|
||||
payloadData.remove(at: 5)
|
||||
}
|
||||
let keyData = key.privateKey
|
||||
|
||||
let privateKey = keyData
|
||||
|
||||
@@ -148,6 +148,10 @@ class FindMyController: ObservableObject {
|
||||
|
||||
} catch {
|
||||
print("Failed with error \(error)")
|
||||
if jsonData.isEmpty {
|
||||
print("Empty response, consider updating your Search Party Token")
|
||||
completion(FindMyErrors.invalidSearchPartyToken)
|
||||
}
|
||||
devices[deviceIndex].reports = []
|
||||
}
|
||||
fetchReportGroup.leave()
|
||||
@@ -241,4 +245,5 @@ class FindMyController: ObservableObject {
|
||||
enum FindMyErrors: Error {
|
||||
case decodingPlistFailed(message: String)
|
||||
case objectReleased
|
||||
case invalidSearchPartyToken
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@ import OSLog
|
||||
import SwiftUI
|
||||
|
||||
class AccessoryController: ObservableObject {
|
||||
@AppStorage("searchPartyToken") private var searchPartyToken: String = ""
|
||||
@Published var accessories: [Accessory]
|
||||
var selfObserver: AnyCancellable?
|
||||
var listElementsObserver = [AnyCancellable]()
|
||||
let findMyController: FindMyController
|
||||
|
||||
weak var savePanel: NSSavePanel?
|
||||
|
||||
init(accessories: [Accessory], findMyController: FindMyController) {
|
||||
self.accessories = accessories
|
||||
self.findMyController = findMyController
|
||||
@@ -95,35 +98,103 @@ class AccessoryController: ObservableObject {
|
||||
|
||||
/// Export the accessories property list so it can be imported at another location.
|
||||
func export(accessories: [Accessory]) throws -> URL {
|
||||
let propertyList = try PropertyListEncoder().encode(accessories)
|
||||
|
||||
let savePanel = NSSavePanel()
|
||||
savePanel.allowedFileTypes = ["plist"]
|
||||
// savePanel.allowedFileTypes = ["plist", "json"]
|
||||
if #available(macOS 12.0, *) {
|
||||
savePanel.allowedContentTypes = [.propertyList]
|
||||
} else {
|
||||
savePanel.allowedFileTypes = ["plist"]
|
||||
}
|
||||
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.directoryURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
|
||||
savePanel.message = "This export contains all private keys! Keep the file save to protect your location data"
|
||||
savePanel.nameFieldLabel = "Filename"
|
||||
savePanel.nameFieldStringValue = "openhaystack_accessories.plist"
|
||||
savePanel.nameFieldStringValue = "openhaystack_accessories"
|
||||
savePanel.prompt = "Export"
|
||||
savePanel.title = "Export accessories & keys"
|
||||
savePanel.isExtensionHidden = false
|
||||
|
||||
let accessoryView = NSView()
|
||||
let popUpButton = NSPopUpButton(title: "File type", target: self, action: #selector(exportFileTypeChanged(button:)))
|
||||
popUpButton.addItems(withTitles: ["Property List", "JSON"])
|
||||
popUpButton.selectItem(at: 0)
|
||||
popUpButton.stringValue = "File type"
|
||||
popUpButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
accessoryView.addSubview(popUpButton)
|
||||
|
||||
let popUpButtonLabel = NSTextField(labelWithString: "File type")
|
||||
popUpButtonLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
accessoryView.addSubview(popUpButtonLabel)
|
||||
accessoryView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
// popUpButtonLabel.leadingAnchor.constraint(greaterThanOrEqualTo: accessoryView.leadingAnchor, constant: 20.0).isActive = true
|
||||
popUpButtonLabel.trailingAnchor.constraint(equalTo: popUpButton.leadingAnchor, constant: -8.0).isActive = true
|
||||
popUpButtonLabel.trailingAnchor.constraint(lessThanOrEqualTo: accessoryView.centerXAnchor, constant: 0).isActive = true
|
||||
popUpButtonLabel.centerYAnchor.constraint(equalTo: popUpButton.centerYAnchor, constant: 0).isActive = true
|
||||
// popUpButton.trailingAnchor.constraint(lessThanOrEqualTo: accessoryView.trailingAnchor, constant: -20.0).isActive = true
|
||||
popUpButton.leadingAnchor.constraint(lessThanOrEqualTo: accessoryView.centerXAnchor, constant: 0).isActive = true
|
||||
popUpButton.topAnchor.constraint(equalTo: accessoryView.topAnchor, constant: 8.0).isActive = true
|
||||
popUpButton.bottomAnchor.constraint(equalTo: accessoryView.bottomAnchor, constant: -8.0).isActive = true
|
||||
popUpButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 20.0).isActive = true
|
||||
popUpButton.widthAnchor.constraint(lessThanOrEqualToConstant: 200.0).isActive = true
|
||||
|
||||
savePanel.accessoryView = accessoryView
|
||||
self.savePanel = savePanel
|
||||
|
||||
let result = savePanel.runModal()
|
||||
|
||||
if result == .OK,
|
||||
let url = savePanel.url
|
||||
var url = savePanel.url
|
||||
{
|
||||
let selectedItemIndex = popUpButton.indexOfSelectedItem
|
||||
|
||||
// Store the accessory file
|
||||
try propertyList.write(to: url)
|
||||
if selectedItemIndex == 0 {
|
||||
if url.pathExtension != "plist" {
|
||||
url = url.appendingPathExtension("plist")
|
||||
}
|
||||
let propertyList = try PropertyListEncoder().encode(accessories)
|
||||
try propertyList.write(to: url)
|
||||
} else if selectedItemIndex == 1 {
|
||||
if url.pathExtension != "json" {
|
||||
url = url.appendingPathExtension("json")
|
||||
}
|
||||
let jsonObject = try JSONEncoder().encode(accessories)
|
||||
try jsonObject.write(to: url)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
throw ImportError.cancelled
|
||||
}
|
||||
|
||||
@objc func exportFileTypeChanged(button: NSPopUpButton) {
|
||||
if button.indexOfSelectedItem == 0 {
|
||||
if #available(macOS 12.0, *) {
|
||||
self.savePanel?.allowedContentTypes = [.propertyList]
|
||||
} else {
|
||||
self.savePanel?.allowedFileTypes = ["plist"]
|
||||
}
|
||||
} else {
|
||||
if #available(macOS 12.0, *) {
|
||||
self.savePanel?.allowedContentTypes = [.json]
|
||||
} else {
|
||||
self.savePanel?.allowedFileTypes = ["json"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Let the user select a file to import the accessories exported by another OpenHaystack instance.
|
||||
func importAccessories() throws {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.allowedFileTypes = ["plist"]
|
||||
if #available(macOS 12.0, *) {
|
||||
openPanel.allowedContentTypes = [.json, .propertyList]
|
||||
} else {
|
||||
openPanel.allowedFileTypes = ["json", "plist"]
|
||||
}
|
||||
|
||||
openPanel.canCreateDirectories = true
|
||||
openPanel.directoryURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
|
||||
openPanel.message = "Import an accessories file that includes the private keys"
|
||||
@@ -134,8 +205,13 @@ class AccessoryController: ObservableObject {
|
||||
if result == .OK,
|
||||
let url = openPanel.url
|
||||
{
|
||||
let propertyList = try Data(contentsOf: url)
|
||||
var importedAccessories = try PropertyListDecoder().decode([Accessory].self, from: propertyList)
|
||||
let accessoryData = try Data(contentsOf: url)
|
||||
var importedAccessories: [Accessory]
|
||||
if url.pathExtension == "plist" {
|
||||
importedAccessories = try PropertyListDecoder().decode([Accessory].self, from: accessoryData)
|
||||
} else {
|
||||
importedAccessories = try JSONDecoder().decode([Accessory].self, from: accessoryData)
|
||||
}
|
||||
|
||||
var updatedAccessories = self.accessories
|
||||
// Filter out accessories with the same id (no duplicates)
|
||||
@@ -169,10 +245,8 @@ class AccessoryController: ObservableObject {
|
||||
case .failure(_):
|
||||
completion(.failure(.activatePlugin))
|
||||
case .success(let accountData):
|
||||
|
||||
guard let token = accountData.searchPartyToken,
|
||||
token.isEmpty == false
|
||||
else {
|
||||
let token = accountData.searchPartyToken ?? self.searchPartyToken.data(using: .utf8) ?? Data()
|
||||
if token.isEmpty {
|
||||
completion(.failure(.searchPartyToken))
|
||||
return
|
||||
}
|
||||
@@ -181,7 +255,12 @@ class AccessoryController: ObservableObject {
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
os_log(.error, "Downloading reports failed %@", error.localizedDescription)
|
||||
completion(.failure(.downloadingReportsFailed))
|
||||
switch error {
|
||||
case FindMyErrors.invalidSearchPartyToken:
|
||||
completion(.failure(.invalidSearchPartyToken))
|
||||
default:
|
||||
completion(.failure(.downloadingReportsFailed))
|
||||
}
|
||||
case .success(let devices):
|
||||
let reports = devices.compactMap({ $0.reports }).flatMap({ $0 })
|
||||
if reports.isEmpty {
|
||||
|
||||
@@ -48,7 +48,7 @@ class AccessoryNearbyMonitor: BluetoothAccessoryDelegate {
|
||||
accessory.lastAdvertisement = Date()
|
||||
}
|
||||
|
||||
func removeNearbyAccessories(now: Date = Date(), timeout: TimeInterval = 10.0) {
|
||||
func removeNearbyAccessories(now: Date = Date(), timeout: TimeInterval = 120.0) {
|
||||
let nearbyAccessories = self.accessoryController.accessories.filter({ $0.isNearby })
|
||||
for accessory in nearbyAccessories {
|
||||
guard let lastAdvertisement = accessory.lastAdvertisement else {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||||
//
|
||||
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
|
||||
// Copyright © 2021 The Open Wireless Link Project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
/// A hexadecimal string representation of the bytes.
|
||||
func hexEncodedString() -> String {
|
||||
let hexDigits = Array("0123456789abcdef".utf16)
|
||||
var hexChars = [UTF16.CodeUnit]()
|
||||
hexChars.reserveCapacity(count * 2)
|
||||
|
||||
for byte in self {
|
||||
let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
|
||||
hexChars.append(hexDigits[index1])
|
||||
hexChars.append(hexDigits[index2])
|
||||
}
|
||||
|
||||
return String(utf16CodeUnits: hexChars, count: hexChars.count)
|
||||
}
|
||||
}
|
||||
120
OpenHaystack/OpenHaystack/HaystackApp/Firmwares/NRF/flash_nrf.py
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/bin/python3
|
||||
from pynrfjprog import LowLevel
|
||||
from intelhex import IntelHex
|
||||
from base64 import b64decode
|
||||
import argparse
|
||||
|
||||
|
||||
def flash_openhaystack_fw(public_key, symmetric_key, update_interval, hex_path, snr=None):
|
||||
"""
|
||||
Flash openhaystack firmware to device
|
||||
@param (optional) int snr: Specify serial number of DK to run example on.
|
||||
"""
|
||||
# Check if paramters are valid
|
||||
if len(public_key) != 57:
|
||||
pk_len = len(public_key)
|
||||
print(f'[!] Public key should be 57 bytes but is {pk_len} bytes')
|
||||
exit(-1)
|
||||
|
||||
if len(symmetric_key) != 32:
|
||||
sk_len = len(symmetric_key)
|
||||
print(f'[!] Symmetric key should be 32 bytes but is {sk_len} bytes')
|
||||
exit(-1)
|
||||
|
||||
if not 0 < update_interval < 4294967295:
|
||||
print(f'[!] Update interval is {update_interval}, but must be bigger than 0 but smaller than 4294967295 (0xFFFFFFFF)')
|
||||
exit(-1)
|
||||
|
||||
# Detect the device family of your device. Initialize an API object with UNKNOWN family and read the device's
|
||||
# family. This step is performed so this example can be run in all devices without customer input.
|
||||
print('[*] Opening API with device family UNKNOWN, reading the device family.')
|
||||
with LowLevel.API(
|
||||
# Using with construction so there is no need to open or close the API class.
|
||||
LowLevel.DeviceFamily.UNKNOWN) as api:
|
||||
if snr is not None:
|
||||
api.connect_to_emu_with_snr(snr)
|
||||
else:
|
||||
api.connect_to_emu_without_snr()
|
||||
device_family = api.read_device_family()
|
||||
|
||||
print(f'[*] Opening API with device family {device_family}, reading the device version.')
|
||||
with LowLevel.API(device_family) as api:
|
||||
# Open the loaded DLL and connect to an emulator probe. If several are connected a pop up will appear.
|
||||
if snr is not None:
|
||||
api.connect_to_emu_with_snr(snr)
|
||||
else:
|
||||
api.connect_to_emu_without_snr()
|
||||
device_version = api.read_device_version()
|
||||
|
||||
print(f'[*] Device version {device_version}')
|
||||
# Select hex file according to device family and device version
|
||||
hex_file_path = f'{hex_path}{device_family}_{device_version.split("_")[0]}_openHayStack.hex'
|
||||
|
||||
print(f'[*] Patching hex file \'{hex_file_path}\' with supplied keys')
|
||||
|
||||
# Open hex file and patch cryptographic keys
|
||||
ih = IntelHex(hex_file_path)
|
||||
|
||||
sk_address = ih.find(b'OFFLINEFINDINGSYMMETRICKEYHERE!')
|
||||
print(f'[*] SK address in hex file is {sk_address}')
|
||||
ih.puts(sk_address, symmetric_key)
|
||||
|
||||
pk_address = ih.find(b'OFFLINEFINDINGUNCOMPRESSEDPUBLICKEYHERE!AAAAAAAAAAAAAAAAA')
|
||||
print(f'[*] PK address in hex file is {pk_address}')
|
||||
ih.puts(pk_address, public_key)
|
||||
|
||||
update_interval_address = ih.find(b'\x37\x33\x33\x31')
|
||||
if update_interval_address - pk_address != 60:
|
||||
print(f'[!] {update_interval_address - pk_address} bytes between update interval and private key, but should be 60 bytes')
|
||||
exit(-1)
|
||||
print(f'[*] Update Interval address in hex file is {update_interval_address}')
|
||||
update_interval_hex = (update_interval).to_bytes(4, byteorder='little')
|
||||
ih.puts(update_interval_address, update_interval_hex)
|
||||
|
||||
# Initialize an API object with the target family. This will load nrfjprog.dll with the proper target family.
|
||||
api = LowLevel.API(device_family)
|
||||
# Open the loaded DLL and connect to an emulator probe. If several are connected a pop up will appear.
|
||||
api.open()
|
||||
try:
|
||||
if snr is not None:
|
||||
api.connect_to_emu_with_snr(snr)
|
||||
else:
|
||||
api.connect_to_emu_without_snr()
|
||||
|
||||
# Just for info
|
||||
device_version = api.read_device_version()
|
||||
print(f'[*] Device version {device_version}')
|
||||
|
||||
# Erase all the flash of the device
|
||||
print('[*] Erasing all flash in the microcontroller.')
|
||||
api.erase_all()
|
||||
|
||||
# Program the parsed hex into the device's memory
|
||||
print(f'[*] Writing patched {hex_file_path} to device.')
|
||||
for segment in ih.segments():
|
||||
api.write(segment[0], ih.gets(segment[0], segment[1] - segment[0]), True)
|
||||
|
||||
# Reset the device and run.
|
||||
api.sys_reset()
|
||||
api.go()
|
||||
print('[*] Program started')
|
||||
|
||||
# Close the loaded DLL to free resources.
|
||||
api.close()
|
||||
|
||||
print('[*] Flashed openHayStack Firmware successfully')
|
||||
|
||||
except LowLevel.APIError:
|
||||
api.close()
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Parse arguments given when calling the script via command line
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-pk', '--public-key', help="Base64 encoded Public key (29 bytes)", required=True)
|
||||
parser.add_argument('-sk', '--symmetric-key', help="Base64 encoded Symmetric key (32 bytes)", required=True)
|
||||
parser.add_argument('-ui', '--update-interval', help="Update interval for key derivation in minutes", required=True, type=int)
|
||||
parser.add_argument('-ph', '--path-to-hex', help="Path to hexfile, defaults to script folder", default="")
|
||||
args = vars(parser.parse_args())
|
||||
flash_openhaystack_fw(public_key=b64decode(args['public_key']), symmetric_key=b64decode(args['symmetric_key']), update_interval=args['update_interval'], hex_path=args['path_to_hex'])
|
||||
136
OpenHaystack/OpenHaystack/HaystackApp/Firmwares/NRF/flash_nrf.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/bin/bash
|
||||
|
||||
cleanup() {
|
||||
echo "### done"
|
||||
}
|
||||
|
||||
|
||||
# Parameter parsing
|
||||
while [[ $# -gt 0 ]]; do
|
||||
KEY="$1"
|
||||
case "$KEY" in
|
||||
-v|--venvdir)
|
||||
VENV_DIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "flash_nrf.sh - Flash the OpenHaystack firmware onto a nRF board"
|
||||
echo ""
|
||||
echo " This script will create a virtual environment for the required tools."
|
||||
echo ""
|
||||
echo "Call: flash_nrf.sh [-v <dir>] PUBLIC_KEY SYMMETRIC_KEY UPDATE_INTERVAL"
|
||||
echo ""
|
||||
echo "Required Arguments:"
|
||||
echo " PUBLIC_KEY"
|
||||
echo " The base64-encoded public key"
|
||||
echo " SYMMETRIC_KEY"
|
||||
echo " The base64-encoded symmetric key"
|
||||
echo " UPDATE_INTERVAL"
|
||||
echo " Refresh interval for key derivation in minutes"
|
||||
echo ""
|
||||
echo "Optional Arguments:"
|
||||
echo " -h, --help"
|
||||
echo " Show this message and exit."
|
||||
echo " -v, --venvdir <dir>"
|
||||
echo " Select Python virtual environment with esptool installed."
|
||||
echo " If the directory does not exist, it will be created."
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
PUBKEY="$1"
|
||||
shift
|
||||
|
||||
if [[ -z "$SYMKEY" ]]; then
|
||||
SYMKEY="$1"
|
||||
shift
|
||||
|
||||
if [[ -z "$UPDATE_INTERVAL" ]]; then
|
||||
UPDATE_INTERVAL="$1"
|
||||
shift
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Defaults: Directory for the virtual environment
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
# Sanity check: Pubkey exists
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
echo "Missing public key, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sanity check: Symmetric key exists
|
||||
if [[ -z "$SYMKEY" ]]; then
|
||||
echo "Missing symmetric key, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#Sanity check: update Interval exists
|
||||
if [[ -z "$UPDATE_INTERVAL" ]]; then
|
||||
echo "Missing update interval, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Setup the virtual environment
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
# Create the virtual environment
|
||||
echo "# Setting up python env in folder $VENV_DIR"
|
||||
PYTHON="$(which python3)"
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
PYTHON="$(which python)"
|
||||
fi
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
echo "Could not find a Python installation, please install Python 3."
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -V 2>&1 | grep "Python 3" > /dev/null); then
|
||||
echo "Executing \"$PYTHON\" does not run Python 3, please make sure that python3 or python on your PATH points to Python 3"
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -c "import venv" &> /dev/null); then
|
||||
echo "Python 3 module \"venv\" was not found."
|
||||
exit 1
|
||||
fi
|
||||
$PYTHON -m venv "$VENV_DIR"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Creating the virtual environment in $VENV_DIR failed."
|
||||
exit 1
|
||||
fi
|
||||
echo "# Activate venv and install pynrfjprog and intelhex"
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install --upgrade pip
|
||||
pip install pynrfjprog && pip install intelhex
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not install Python 3 module pynrfjprog in $VENV_DIR";
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
source "$VENV_DIR/bin/activate"
|
||||
fi
|
||||
|
||||
# Call flash_nrf.py. Errors from here on are critical
|
||||
set -e
|
||||
trap cleanup INT TERM EXIT
|
||||
echo "### Executing python script ###"
|
||||
python3 "$(dirname "$0")"/flash_nrf.py --public-key $PUBKEY --symmetric-key $SYMKEY --update-interval $UPDATE_INTERVAL --path-to-hex "$(dirname "$0")"/
|
||||
echo "### Python script finished ###"
|
||||
@@ -31,6 +31,11 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
@Published var name: String
|
||||
let id: Int
|
||||
let privateKey: Data
|
||||
let symmetricKey: Data
|
||||
@Published var usesDerivation: Bool
|
||||
@Published var oldestRelevantSymmetricKey: Data
|
||||
@Published var lastDerivationTimestamp: Date
|
||||
@Published var updateInterval: TimeInterval
|
||||
@Published var locations: [FindMyLocationReport]?
|
||||
@Published var color: Color
|
||||
@Published var icon: String
|
||||
@@ -41,6 +46,10 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
// Reset active status if deployed
|
||||
if !wasDeployed && isDeployed {
|
||||
self.isActive = false
|
||||
self.usesDerivation = false
|
||||
} else if wasDeployed && !isDeployed {
|
||||
self.usesDerivation = false
|
||||
self.updateInterval = TimeInterval(60 * 60 * 24)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +72,14 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
}
|
||||
self.id = key.hashValue
|
||||
self.privateKey = key
|
||||
let symKey = SymmetricKey(size: .bits256)
|
||||
self.symmetricKey = symKey.withUnsafeBytes {
|
||||
return Data(Array($0))
|
||||
}
|
||||
self.usesDerivation = false
|
||||
self.oldestRelevantSymmetricKey = self.symmetricKey
|
||||
self.lastDerivationTimestamp = Date()
|
||||
self.updateInterval = TimeInterval(60 * 60)
|
||||
self.color = color
|
||||
self.icon = iconName
|
||||
self.isDeployed = false
|
||||
@@ -73,6 +90,12 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
self.name = try container.decode(String.self, forKey: .name)
|
||||
self.id = try container.decode(Int.self, forKey: .id)
|
||||
self.privateKey = try container.decode(Data.self, forKey: .privateKey)
|
||||
let symmetricKey = (try? container.decode(Data.self, forKey: .symmetricKey)) ?? SymmetricKey(size: .bits256).withUnsafeBytes { return Data($0) }
|
||||
self.symmetricKey = symmetricKey
|
||||
self.usesDerivation = (try? container.decode(Bool.self, forKey: .usesDerivation)) ?? false
|
||||
self.oldestRelevantSymmetricKey = (try? container.decode(Data.self, forKey: .oldestRelevantSymmetricKey)) ?? symmetricKey
|
||||
self.lastDerivationTimestamp = (try? container.decode(Date.self, forKey: .lastDerivationTimestamp)) ?? Date()
|
||||
self.updateInterval = (try? container.decode(TimeInterval.self, forKey: .updateInterval)) ?? TimeInterval(60 * 60 * 24)
|
||||
self.icon = (try? container.decode(String.self, forKey: .icon)) ?? ""
|
||||
self.isDeployed = (try? container.decode(Bool.self, forKey: .isDeployed)) ?? false
|
||||
self.isActive = (try? container.decode(Bool.self, forKey: .isActive)) ?? false
|
||||
@@ -93,6 +116,11 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
try container.encode(self.name, forKey: .name)
|
||||
try container.encode(self.id, forKey: .id)
|
||||
try container.encode(self.privateKey, forKey: .privateKey)
|
||||
try container.encode(self.symmetricKey, forKey: .symmetricKey)
|
||||
try container.encode(self.usesDerivation, forKey: .usesDerivation)
|
||||
try container.encode(self.oldestRelevantSymmetricKey, forKey: .oldestRelevantSymmetricKey)
|
||||
try container.encode(self.lastDerivationTimestamp, forKey: .lastDerivationTimestamp)
|
||||
try container.encode(self.updateInterval, forKey: .updateInterval)
|
||||
try container.encode(self.icon, forKey: .icon)
|
||||
try container.encode(self.isDeployed, forKey: .isDeployed)
|
||||
try container.encode(self.isActive, forKey: .isActive)
|
||||
@@ -114,6 +142,15 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
return publicKey
|
||||
}
|
||||
|
||||
/// Get Uncompressed public key
|
||||
/// This is needed for libraries such as mbedtls that do not support loading compressed points
|
||||
func getUncompressedPublicKey() throws -> Data {
|
||||
guard let publicKey = BoringSSL.deriveUncompressedPublicKey(fromPrivateKey: self.privateKey) else {
|
||||
throw KeyError.keyDerivationFailed
|
||||
}
|
||||
return publicKey
|
||||
}
|
||||
|
||||
func getAdvertisementKey() throws -> Data {
|
||||
guard var publicKey = BoringSSL.derivePublicKey(fromPrivateKey: self.privateKey) else {
|
||||
throw KeyError.keyDerivationFailed
|
||||
@@ -147,30 +184,140 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
return Data(digest)
|
||||
}
|
||||
|
||||
func getNewestSymmetricKey() -> Data {
|
||||
var derivationTimestamp = self.lastDerivationTimestamp
|
||||
var symmetricKey = self.oldestRelevantSymmetricKey
|
||||
while derivationTimestamp < Date() {
|
||||
derivationTimestamp.addTimeInterval(self.updateInterval)
|
||||
symmetricKey = Accessory.kdf(inputData: self.symmetricKey, sharedInfo: "update".data(using: .ascii)!, bytesToReturn: 32)
|
||||
}
|
||||
return symmetricKey
|
||||
}
|
||||
|
||||
func toFindMyDevice() throws -> FindMyDevice {
|
||||
|
||||
let findMyKey = FindMyKey(
|
||||
advertisedKey: try self.getAdvertisementKey(),
|
||||
hashedKey: try self.hashedPublicKey(),
|
||||
privateKey: self.privateKey,
|
||||
startTime: nil,
|
||||
duration: nil,
|
||||
pu: nil,
|
||||
yCoordinate: nil,
|
||||
fullKey: nil)
|
||||
var findMyKey = [FindMyKey]()
|
||||
|
||||
/// Always append first FindMyKey to support devices without derivation
|
||||
findMyKey.append(
|
||||
FindMyKey(
|
||||
advertisedKey: try self.getAdvertisementKey(),
|
||||
hashedKey: try self.hashedPublicKey(),
|
||||
privateKey: self.privateKey,
|
||||
startTime: nil,
|
||||
duration: nil,
|
||||
pu: nil,
|
||||
yCoordinate: nil,
|
||||
fullKey: nil)
|
||||
)
|
||||
if self.usesDerivation {
|
||||
/// Derive FindMyKeys until we have symmetric key from one week before now
|
||||
while self.lastDerivationTimestamp < Date() - TimeInterval(7 * 24 * 60 * 60) {
|
||||
self.lastDerivationTimestamp.addTimeInterval(self.updateInterval)
|
||||
self.oldestRelevantSymmetricKey = Accessory.kdf(inputData: self.oldestRelevantSymmetricKey, sharedInfo: "update".data(using: .ascii)!, bytesToReturn: 32)
|
||||
}
|
||||
|
||||
/// we need to generate Keys from seven days in the past until now and 10 extra keys in case of desynchronization
|
||||
let untilDate = Date() + TimeInterval(self.updateInterval * 11)
|
||||
var derivationTimestamp = self.lastDerivationTimestamp
|
||||
var derivedSymmetricKey = self.oldestRelevantSymmetricKey
|
||||
|
||||
print("--- Derived keys for \(self.name) ---")
|
||||
print("Masterbacon symmetric key \(self.symmetricKey.hexEncodedString())")
|
||||
do {
|
||||
let uncompressedMasterBeaconKey = try self.getUncompressedPublicKey()
|
||||
print("Masterbeacon public key (uncompressed) \(uncompressedMasterBeaconKey.hexEncodedString())")
|
||||
} catch {
|
||||
print("Failed to get master beacon public key (only needed for printing)")
|
||||
}
|
||||
|
||||
while derivationTimestamp < untilDate {
|
||||
/// Step 1: derive SKN_i
|
||||
derivedSymmetricKey = Accessory.kdf(inputData: derivedSymmetricKey, sharedInfo: "update".data(using: .ascii)!, bytesToReturn: 32)
|
||||
/// Step 2: derive u_i and v_i
|
||||
let derivedAntiTrackingKeys = Accessory.kdf(inputData: derivedSymmetricKey, sharedInfo: "diversify".data(using: .ascii)!, bytesToReturn: 72)
|
||||
/// Step 3 & 4: compute private and public key
|
||||
guard let derivedPrivateKey = BoringSSL.calculatePrivateKey(fromSharedData: derivedAntiTrackingKeys, masterBeaconPrivateKey: self.privateKey) else {
|
||||
throw KeyError.keyDerivationFailed
|
||||
}
|
||||
guard let derivedPublicKey = BoringSSL.derivePublicKey(fromPrivateKey: derivedPrivateKey) else {
|
||||
throw KeyError.keyDerivationFailed
|
||||
}
|
||||
|
||||
/// Drop first byte to get advertisment key
|
||||
let derivedAdvertisementKey = derivedPublicKey.dropFirst()
|
||||
guard derivedAdvertisementKey.count == 28 else { throw KeyError.keyDerivationFailed }
|
||||
|
||||
/// Get hash of advertisment key
|
||||
var sha = SHA256()
|
||||
sha.update(data: derivedAdvertisementKey)
|
||||
let derivedAdvertisementKeyHash = Data(sha.finalize())
|
||||
|
||||
print("-> Derived keys for \(derivationTimestamp):")
|
||||
//print("Dervided anti tracking keys \(derivedAntiTrackingKeys.hexEncodedString())")
|
||||
//print("SymmetricKey \(derivedSymmetricKey.hexEncodedString())")
|
||||
print("Derived public key \(derivedPublicKey.hexEncodedString())")
|
||||
|
||||
findMyKey.append(
|
||||
FindMyKey(
|
||||
advertisedKey: derivedAdvertisementKey,
|
||||
hashedKey: derivedAdvertisementKeyHash,
|
||||
privateKey: derivedPrivateKey,
|
||||
startTime: nil,
|
||||
duration: nil,
|
||||
pu: nil,
|
||||
yCoordinate: nil,
|
||||
fullKey: nil)
|
||||
)
|
||||
|
||||
/// Add time interval to derivation timestamp
|
||||
derivationTimestamp.addTimeInterval(self.updateInterval)
|
||||
}
|
||||
}
|
||||
|
||||
return FindMyDevice(
|
||||
deviceId: String(self.id),
|
||||
keys: [findMyKey],
|
||||
keys: findMyKey,
|
||||
catalinaBigSurKeyFiles: nil,
|
||||
reports: nil,
|
||||
decryptedReports: nil)
|
||||
}
|
||||
|
||||
static func kdf(inputData: Data, sharedInfo: Data, bytesToReturn: Int) -> Data {
|
||||
var derivedKey = Data()
|
||||
var counter: Int32 = 1
|
||||
|
||||
/// derive from input and shared info until we have enough data
|
||||
while derivedKey.count < bytesToReturn {
|
||||
var shaDigest = SHA256()
|
||||
shaDigest.update(data: inputData)
|
||||
let counterData = Data(Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter)).reversed())
|
||||
shaDigest.update(data: counterData)
|
||||
shaDigest.update(data: sharedInfo)
|
||||
derivedKey.append(Data(shaDigest.finalize()))
|
||||
counter += 1
|
||||
}
|
||||
|
||||
/// drop bytes which are not needed and return
|
||||
derivedKey = derivedKey.dropLast(derivedKey.count - bytesToReturn)
|
||||
return derivedKey
|
||||
}
|
||||
|
||||
func resetDerivationState() {
|
||||
/// reset keys and derivation time in case an accessory is reflashed with old keys
|
||||
self.oldestRelevantSymmetricKey = self.symmetricKey
|
||||
self.lastDerivationTimestamp = Date()
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case id
|
||||
case privateKey
|
||||
case usesDerivation
|
||||
case symmetricKey
|
||||
case oldestRelevantSymmetricKey
|
||||
case lastDerivationTimestamp
|
||||
case updateInterval
|
||||
case colorComponents
|
||||
case colorSpaceName
|
||||
case icon
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreLocation
|
||||
|
||||
// swiftlint:disable force_try
|
||||
struct PreviewData {
|
||||
|
||||
72
OpenHaystack/OpenHaystack/HaystackApp/NRFController.swift
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||||
//
|
||||
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
|
||||
// Copyright © 2021 The Open Wireless Link Project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct NRFController {
|
||||
|
||||
static var nrfFirmwareDirectory: URL? {
|
||||
Bundle.main.resourceURL?.appendingPathComponent("NRF")
|
||||
}
|
||||
|
||||
/// Runs the script to flash the firmware onto an nRF Device.
|
||||
static func flashToNRF(accessory: Accessory, updateInterval: Int, completion: @escaping (ClosureResult) -> Void) throws {
|
||||
// Copy firmware to a temporary directory
|
||||
let temp = NSTemporaryDirectory() + "OpenHaystack"
|
||||
let urlTemp = URL(fileURLWithPath: temp)
|
||||
try? FileManager.default.removeItem(at: urlTemp)
|
||||
|
||||
try? FileManager.default.createDirectory(atPath: temp, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
guard let nrfDirectory = nrfFirmwareDirectory else { return }
|
||||
|
||||
try FileManager.default.copyFolder(from: nrfDirectory, to: urlTemp)
|
||||
let urlScript = urlTemp.appendingPathComponent("flash_nrf.sh")
|
||||
try FileManager.default.setAttributes([FileAttributeKey.posixPermissions: 0o755], ofItemAtPath: urlScript.path)
|
||||
try FileManager.default.setAttributes([FileAttributeKey.posixPermissions: 0o755], ofItemAtPath: urlTemp.appendingPathComponent("flash_nrf.py").path)
|
||||
|
||||
// Get public key, newest relevant symmetric key and updateInterval for flashing
|
||||
let masterBeaconPublicKey = try accessory.getUncompressedPublicKey()
|
||||
let masterBeaconSymmetricKey = accessory.getNewestSymmetricKey()
|
||||
let arguments = [masterBeaconPublicKey.base64EncodedString(), masterBeaconSymmetricKey.base64EncodedString(), String(updateInterval)]
|
||||
|
||||
// Create file for logging and get file handle
|
||||
let loggingFileUrl = urlTemp.appendingPathComponent("nrf_installer.log")
|
||||
try "".write(to: loggingFileUrl, atomically: true, encoding: .utf8)
|
||||
let loggingFileHandle = FileHandle.init(forWritingAtPath: loggingFileUrl.path)!
|
||||
|
||||
// Run script
|
||||
let task = try NSUserUnixTask(url: urlScript)
|
||||
task.standardOutput = loggingFileHandle
|
||||
task.standardError = loggingFileHandle
|
||||
task.execute(withArguments: arguments) { e in
|
||||
DispatchQueue.main.async {
|
||||
if let error = e {
|
||||
completion(.failure(loggingFileUrl, error))
|
||||
} else {
|
||||
completion(.success(loggingFileUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try loggingFileHandle.close()
|
||||
}
|
||||
}
|
||||
|
||||
enum ClosureResult {
|
||||
case success(URL)
|
||||
case failure(URL, Error)
|
||||
}
|
||||
|
||||
enum NRFFirmwareFlashError: Error {
|
||||
/// Missing files for flashing
|
||||
case notFound
|
||||
/// Flashing / writing failed
|
||||
case flashFailed
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||||
//
|
||||
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
|
||||
// Copyright © 2021 The Open Wireless Link Project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
|
||||
/// Can check if a new OpenHaystack version is needed and download it.
|
||||
public struct UpdateCheckController {
|
||||
|
||||
public static func checkForNewVersion() {
|
||||
// Load the GitHub Releases page
|
||||
let releasesURL = URL(string: "https://github.com/seemoo-lab/openhaystack/releases")!
|
||||
URLSession.shared.dataTask(with: releasesURL) { optionalData, response, error in
|
||||
guard let data = optionalData,
|
||||
(response as? HTTPURLResponse)?.statusCode == 200,
|
||||
let htmlString = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let availableVersion = getVersion(from: htmlString) else {
|
||||
return
|
||||
}
|
||||
|
||||
//Get installed version
|
||||
let version = Bundle.main.infoDictionary?["CFBundleVersionShortString"] as? String ?? "0"
|
||||
|
||||
let comparisonResult = compareVersions(availableVersion: availableVersion, installedVersion: version)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if comparisonResult == .older, askToDownloadUpdate() == .alertSecondButtonReturn {
|
||||
//The currently installed version is older. Install an update
|
||||
self.downloadUpdate(
|
||||
version: availableVersion,
|
||||
finished: { success in
|
||||
if success {
|
||||
let result = successDownloadAlert()
|
||||
if result == .alertSecondButtonReturn {
|
||||
//Open the download folder
|
||||
let downloadURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
|
||||
NSWorkspace.shared.open(downloadURL)
|
||||
}
|
||||
} else {
|
||||
if downloadFailedAlert() == .alertSecondButtonReturn {
|
||||
NSWorkspace.shared.open(URL(string: "https://github.com/seemoo-lab/openhaystack/releases")!)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}.resume()
|
||||
}
|
||||
|
||||
internal static func getVersion(from htmlString: String) -> String? {
|
||||
guard let regex = try? NSRegularExpression(pattern: "Release (v[0-9]+(.[0-9]+)?(.[0-9]+)?)") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let htmlNSString = htmlString as NSString
|
||||
|
||||
let htmlRange = NSRange(location: 0, length: htmlNSString.length)
|
||||
|
||||
if let checkResult = regex.firstMatch(in: htmlNSString as String, options: [], range: htmlRange),
|
||||
checkResult.numberOfRanges >= 2
|
||||
{
|
||||
|
||||
//Get the latest release version range
|
||||
// A result should have multiple ranges for each capture group. 1 is the capture group for the version number
|
||||
let releaseVersionRange = checkResult.range(at: 1)
|
||||
let releaseVersion = htmlNSString.substring(with: releaseVersionRange)
|
||||
|
||||
let releaseVersionNumber = releaseVersion.replacingOccurrences(of: "v", with: "")
|
||||
|
||||
return releaseVersionNumber
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Compares two version strings and returns if the installed version is older, newer or the same
|
||||
/// - Parameters:
|
||||
/// - availableVersion: The latest available version
|
||||
/// - installedVersion: The currently installed version
|
||||
/// - Returns: .older when a newer version is available. .newer when the installed version is newer .same, if both versions are equal
|
||||
internal static func compareVersions(availableVersion: String, installedVersion: String) -> VersionCompare {
|
||||
let availableVersionSplit = availableVersion.split(separator: ".")
|
||||
let installedVersionSplit = installedVersion.split(separator: ".")
|
||||
|
||||
for (idx, availableVersionPart) in availableVersionSplit.enumerated() {
|
||||
|
||||
if idx < installedVersionSplit.count {
|
||||
guard let avpi = Int(availableVersionPart),
|
||||
let ivpi = Int(installedVersionSplit[idx])
|
||||
else { return .older }
|
||||
|
||||
if avpi > ivpi {
|
||||
return .older
|
||||
} else if ivpi > avpi {
|
||||
return .newer
|
||||
}
|
||||
|
||||
} else {
|
||||
//The installed version is x.x
|
||||
// The new version is x.x.y so it must be older
|
||||
return .older
|
||||
}
|
||||
}
|
||||
|
||||
if installedVersionSplit.count > availableVersionSplit.count {
|
||||
//The installed version has a higher sub-version. So it must be newer
|
||||
return .newer
|
||||
}
|
||||
|
||||
// All numbers were equal
|
||||
return .same
|
||||
}
|
||||
|
||||
enum VersionCompare {
|
||||
case same, newer, older
|
||||
}
|
||||
|
||||
static func downloadUpdate(version: String, finished: @escaping (Bool) -> Void) {
|
||||
|
||||
//Download the current version into a file in Downloads
|
||||
let downloadURL = URL(string: "https://github.com/seemoo-lab/openhaystack/releases/download/v\(version)/OpenHaystack.zip")!
|
||||
|
||||
let task = URLSession.shared.downloadTask(with: downloadURL) { optionalFileURL, response, error in
|
||||
|
||||
guard let downloadLocation = optionalFileURL else {
|
||||
finished(false)
|
||||
return
|
||||
}
|
||||
|
||||
//Move the file to the downloads folder
|
||||
let downloadURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
|
||||
let openHaystackURL = downloadURL.appendingPathComponent("OpenHaystack.zip")
|
||||
do {
|
||||
let fm = FileManager.default
|
||||
if fm.fileExists(atPath: openHaystackURL.path) {
|
||||
_ = try fm.replaceItemAt(openHaystackURL, withItemAt: downloadLocation)
|
||||
} else {
|
||||
try fm.moveItem(at: downloadLocation, to: openHaystackURL)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { finished(true) }
|
||||
} catch let error {
|
||||
print(error.localizedDescription)
|
||||
DispatchQueue.main.async { finished(false) }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
}
|
||||
|
||||
private static func askToDownloadUpdate() -> NSApplication.ModalResponse {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("New version available", comment: "Alert title")
|
||||
alert.informativeText = NSLocalizedString("A new version of OpenHaystack is available. Do you want to download it now?", comment: "Alert text")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
alert.addButton(withTitle: "Download")
|
||||
|
||||
return alert.runModal()
|
||||
}
|
||||
|
||||
private static func successDownloadAlert() -> NSApplication.ModalResponse {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("Successfully downloaded update", comment: "Alert title")
|
||||
alert.informativeText = NSLocalizedString("The new version has been downloaded successfully and it was placed in your Downloads folder.", comment: "Alert text")
|
||||
alert.addButton(withTitle: "Okay")
|
||||
alert.addButton(withTitle: "Open folder")
|
||||
|
||||
return alert.runModal()
|
||||
}
|
||||
|
||||
private static func downloadFailedAlert() -> NSApplication.ModalResponse {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("Download failed", comment: "Alert title")
|
||||
alert.informativeText = NSLocalizedString("To update to the newest version, please open the releases page on GitHub", comment: "Alert text")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
alert.addButton(withTitle: "Open")
|
||||
|
||||
return alert.runModal()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension String {
|
||||
func substring(from range: NSRange) -> String {
|
||||
let substring = self[self.index(startIndex, offsetBy: range.lowerBound)..<self.index(startIndex, offsetBy: range.upperBound)]
|
||||
|
||||
return String(substring)
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,15 @@ struct AccessoryListEntry: View {
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
func updateIntervalView() -> some View {
|
||||
let intervalFormatter = DateComponentsFormatter()
|
||||
intervalFormatter.unitsStyle = .abbreviated
|
||||
|
||||
return Group {
|
||||
Text("Key derivation interval: \(intervalFormatter.string(from: accessory.updateInterval)!)")
|
||||
}.font(.footnote)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack {
|
||||
@@ -51,6 +60,9 @@ struct AccessoryListEntry: View {
|
||||
.font(.headline)
|
||||
}
|
||||
self.timestampView()
|
||||
if accessory.usesDerivation {
|
||||
self.updateIntervalView()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
@@ -68,8 +80,11 @@ struct AccessoryListEntry: View {
|
||||
.padding(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0))
|
||||
.contextMenu {
|
||||
Button("Delete", action: { self.delete(accessory) })
|
||||
Divider()
|
||||
Button("Rename", action: { self.editingName = true })
|
||||
Menu("Key derivation options") {
|
||||
Button("Toggle key derivation", action: { accessory.usesDerivation = !accessory.usesDerivation })
|
||||
Button("Reset derivation state", action: { accessory.resetDerivationState() })
|
||||
}
|
||||
Divider()
|
||||
Button("Copy key ID (Base64)", action: { self.copyPublicKeyHash(of: accessory) })
|
||||
Menu("Copy advertisement key") {
|
||||
@@ -77,8 +92,19 @@ struct AccessoryListEntry: View {
|
||||
Button("Byte array", action: { self.copyAdvertisementKey(escapedString: false) })
|
||||
Button("Escaped string", action: { self.copyAdvertisementKey(escapedString: true) })
|
||||
}
|
||||
Menu("Copy symmetric and uncompressed public key") {
|
||||
Button("Base64", action: { self.copySymmetricAndPublicKeyBase64(of: accessory) })
|
||||
Button("Escaped string", action: { self.copySymmetricAndPublicKey(of: accessory) })
|
||||
}
|
||||
Divider()
|
||||
Button("Mark as \(accessory.isDeployed ? "deployable" : "deployed")", action: { accessory.isDeployed.toggle() })
|
||||
|
||||
Group {
|
||||
Button("Copy private Key B64", action: { copyPrivateKey(accessory: accessory) })
|
||||
|
||||
Button("Export Locations", action: { exportLocations(accessory: accessory) })
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +166,56 @@ struct AccessoryListEntry: View {
|
||||
}
|
||||
}
|
||||
|
||||
func copySymmetricAndPublicKey(of accessory: Accessory) {
|
||||
do {
|
||||
let symmetricKey = accessory.symmetricKey
|
||||
let publicKey = try accessory.getUncompressedPublicKey()
|
||||
let publicKeyString = [UInt8](publicKey).map { "\\x\(String($0, radix: 16))" }.joined()
|
||||
let symmetricKeyString = [UInt8](symmetricKey).map { "\\x\(String($0, radix: 16))" }.joined()
|
||||
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.prepareForNewContents(with: .currentHostOnly)
|
||||
pasteboard.setString("Symmetric key: \(symmetricKeyString)\n Uncompressed public key: \(publicKeyString) ", forType: .string)
|
||||
} catch {
|
||||
os_log("Failed extracing public key %@", String(describing: error))
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
func copySymmetricAndPublicKeyBase64(of accessory: Accessory) {
|
||||
do {
|
||||
let symmetricKey = accessory.symmetricKey
|
||||
let publicKey = try accessory.getUncompressedPublicKey()
|
||||
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.prepareForNewContents(with: .currentHostOnly)
|
||||
pasteboard.setString("Symmetric key: \(symmetricKey.base64EncodedString())\n Uncompressed public key: \(publicKey.base64EncodedString()) ", forType: .string)
|
||||
} catch {
|
||||
os_log("Failed extracing public key %@", String(describing: error))
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
func copyPrivateKey(accessory: Accessory) {
|
||||
let privateKey = accessory.privateKey
|
||||
let keyB64 = privateKey.base64EncodedString()
|
||||
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.prepareForNewContents(with: .currentHostOnly)
|
||||
pasteboard.setString(keyB64, forType: .string)
|
||||
}
|
||||
|
||||
func exportLocations(accessory: Accessory) {
|
||||
guard let locations = accessory.locations,
|
||||
let locationData = try? JSONEncoder().encode(locations)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let savePanel = SavePanel.shared
|
||||
savePanel.saveFile(file: locationData, fileExtension: "json")
|
||||
}
|
||||
|
||||
struct AccessoryListEntry_Previews: PreviewProvider {
|
||||
@StateObject static var accessory = PreviewData.accessories.first!
|
||||
@State static var alertType: OpenHaystackMainView.AlertType?
|
||||
@@ -160,6 +236,7 @@ struct AccessoryListEntry: View {
|
||||
get: { accessory.name },
|
||||
set: { accessory.name = $0 }
|
||||
),
|
||||
|
||||
alertType: self.$alertType,
|
||||
delete: { _ in () },
|
||||
deployAccessoryToMicrobit: { _ in () },
|
||||
|
||||
@@ -11,7 +11,7 @@ import AppKit
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
final class ActivityIndicator: NSViewRepresentable {
|
||||
struct ActivityIndicator: NSViewRepresentable {
|
||||
|
||||
init(size: NSControl.ControlSize) {
|
||||
self.size = size
|
||||
|
||||
@@ -19,6 +19,7 @@ struct ManageAccessoriesView: View {
|
||||
|
||||
// MARK: Bindings from main View
|
||||
@Binding var alertType: OpenHaystackMainView.AlertType?
|
||||
@Binding var scriptOutput: String?
|
||||
@Binding var focusedAccessory: Accessory?
|
||||
@Binding var accessoryToDeploy: Accessory?
|
||||
@Binding var showESP32DeploySheet: Bool
|
||||
@@ -48,6 +49,8 @@ struct ManageAccessoriesView: View {
|
||||
switch sheetType {
|
||||
case .esp32Install:
|
||||
ESP32InstallSheet(accessory: self.$accessoryToDeploy, alertType: self.$alertType)
|
||||
case .nrfDeviceInstall:
|
||||
NRFInstallSheet(accessory: self.$accessoryToDeploy, alertType: self.$alertType, scriptOutput: self.$scriptOutput)
|
||||
case .deployFirmware:
|
||||
self.selectTargetView
|
||||
}
|
||||
@@ -148,6 +151,13 @@ struct ManageAccessoriesView: View {
|
||||
)
|
||||
.buttonStyle(LargeButtonStyle())
|
||||
|
||||
Button(
|
||||
"NRF Device",
|
||||
action: {
|
||||
self.sheetShown = .nrfDeviceInstall
|
||||
}
|
||||
).buttonStyle(LargeButtonStyle())
|
||||
|
||||
Button(
|
||||
"Cancel",
|
||||
action: {
|
||||
@@ -257,6 +267,7 @@ struct ManageAccessoriesView: View {
|
||||
return self.rawValue
|
||||
}
|
||||
case esp32Install
|
||||
case nrfDeviceInstall
|
||||
case deployFirmware
|
||||
}
|
||||
}
|
||||
@@ -265,12 +276,15 @@ struct ManageAccessoriesView_Previews: PreviewProvider {
|
||||
|
||||
@State static var accessories = PreviewData.accessories
|
||||
@State static var alertType: OpenHaystackMainView.AlertType?
|
||||
@State static var scriptOutput: String?
|
||||
@State static var focussed: Accessory?
|
||||
@State static var deploy: Accessory?
|
||||
@State static var showESPSheet: Bool = true
|
||||
|
||||
static var previews: some View {
|
||||
ManageAccessoriesView(alertType: self.$alertType, focusedAccessory: self.$focussed, accessoryToDeploy: self.$deploy, showESP32DeploySheet: self.$showESPSheet)
|
||||
ManageAccessoriesView(
|
||||
alertType: self.$alertType, scriptOutput: self.$scriptOutput, focusedAccessory: self.$focussed, accessoryToDeploy: self.$deploy,
|
||||
showESP32DeploySheet: self.$showESPSheet)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||||
//
|
||||
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
|
||||
// Copyright © 2021 The Open Wireless Link Project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
|
||||
struct NRFInstallSheet: View {
|
||||
@Binding var accessory: Accessory?
|
||||
@Binding var alertType: OpenHaystackMainView.AlertType?
|
||||
@Binding var scriptOutput: String?
|
||||
@State var isFlashing = false
|
||||
|
||||
@ObservedObject var days = NumbersOnly()
|
||||
@ObservedObject var hours = NumbersOnly()
|
||||
@ObservedObject var minutes = NumbersOnly()
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
self.flashView
|
||||
.padding()
|
||||
.overlay(self.loadingOverlay)
|
||||
.frame(minWidth: 640, minHeight: 480, alignment: .center)
|
||||
}
|
||||
.onAppear {
|
||||
}
|
||||
}
|
||||
|
||||
var flashView: some View {
|
||||
VStack {
|
||||
Text("Flash your NRF Device")
|
||||
.font(.title2)
|
||||
|
||||
Text("Fill out options for flashing firmware")
|
||||
.foregroundColor(.gray)
|
||||
|
||||
Divider()
|
||||
|
||||
Text(
|
||||
"The new NRF firmware uses rotating keys. This means that the device changes its public key after a specific number of days. This disallows ad networks to track your device over several days when you are moving around the city. Shorter update cycles then days are not supported"
|
||||
)
|
||||
self.timePicker
|
||||
|
||||
Text("One day is a reasonable amount of time")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button(
|
||||
"Deploy",
|
||||
action: {
|
||||
if let accessory = self.accessory {
|
||||
var daysInt = Int(days.value) ?? 1
|
||||
if daysInt < 1 {
|
||||
daysInt = 1
|
||||
}
|
||||
let hoursInt = 0
|
||||
let minutesInt = 0
|
||||
|
||||
let updateInterval = daysInt * 24 * 60 + hoursInt * 60 + minutesInt
|
||||
//warn user if no update interval was given
|
||||
if updateInterval > 0 {
|
||||
deployAccessoryToNRFDevice(accessory: accessory, updateInterval: updateInterval)
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Button(
|
||||
"Cancel",
|
||||
action: {
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Flashing from M1 Macs might fail due to missing ARM support by NRF")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var timePicker: some View {
|
||||
Group {
|
||||
HStack {
|
||||
TextField("", text: $days.value).textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
Text("Day(s)")
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
|
||||
var loadingOverlay: some View {
|
||||
ZStack {
|
||||
if isFlashing {
|
||||
Rectangle()
|
||||
.fill(Color.gray)
|
||||
.opacity(0.5)
|
||||
|
||||
VStack {
|
||||
ActivityIndicator(size: .large)
|
||||
Text("This can take up to 3min")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deployAccessoryToNRFDevice(accessory: Accessory, updateInterval: Int) {
|
||||
do {
|
||||
self.isFlashing = true
|
||||
|
||||
try NRFController.flashToNRF(
|
||||
accessory: accessory,
|
||||
updateInterval: updateInterval,
|
||||
completion: { result in
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
|
||||
self.isFlashing = false
|
||||
switch result {
|
||||
case .success(_):
|
||||
self.alertType = .deployedSuccessfully
|
||||
accessory.isDeployed = true
|
||||
accessory.usesDerivation = true
|
||||
accessory.updateInterval = TimeInterval(updateInterval * 60)
|
||||
case .failure(let loggingFileUrl, let error):
|
||||
os_log(.error, "Flashing to NRF device failed %@", String(describing: error))
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
self.alertType = .nrfDeployFailed
|
||||
do {
|
||||
self.scriptOutput = try String(contentsOf: loggingFileUrl, encoding: .ascii)
|
||||
} catch {
|
||||
self.scriptOutput = "Error while trying to read log file."
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
os_log(.error, "Preparation or execution of script failed %@", String(describing: error))
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
self.alertType = .deployFailed
|
||||
self.isFlashing = false
|
||||
}
|
||||
|
||||
self.accessory = nil
|
||||
}
|
||||
}
|
||||
|
||||
struct NRFInstallSheet_Previews: PreviewProvider {
|
||||
@State static var acc: Accessory? = try! Accessory(name: "Sample")
|
||||
|
||||
@State static var alert: OpenHaystackMainView.AlertType?
|
||||
@State static var scriptOutput: String?
|
||||
|
||||
static var previews: some View {
|
||||
NRFInstallSheet(accessory: $acc, alertType: $alert, scriptOutput: $scriptOutput)
|
||||
}
|
||||
}
|
||||
|
||||
class NumbersOnly: ObservableObject {
|
||||
@Published var value = "1" {
|
||||
didSet {
|
||||
let filtered = value.filter { $0.isNumber }
|
||||
|
||||
if value != filtered {
|
||||
value = filtered
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ struct OpenHaystackMainView: View {
|
||||
@State var alertType: AlertType?
|
||||
@State var popUpAlertType: PopUpAlertType?
|
||||
@State var errorDescription: String?
|
||||
@State var scriptOutput: String?
|
||||
@State var searchPartyToken: String = ""
|
||||
@State var searchPartyTokenLoaded = false
|
||||
@State var mapType: MKMapType = .standard
|
||||
@@ -37,12 +38,16 @@ struct OpenHaystackMainView: View {
|
||||
|
||||
@State var showESP32DeploySheet = false
|
||||
|
||||
@AppStorage("searchPartyToken") private var settingsSPToken: String?
|
||||
@AppStorage("useMailPlugin") private var settingsUseMailPlugin: Bool = false
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationView {
|
||||
|
||||
ManageAccessoriesView(
|
||||
alertType: self.$alertType,
|
||||
scriptOutput: self.$scriptOutput,
|
||||
focusedAccessory: self.$focusedAccessory,
|
||||
accessoryToDeploy: self.$accessoryToDeploy,
|
||||
showESP32DeploySheet: self.$showESP32DeploySheet
|
||||
@@ -133,7 +138,7 @@ struct OpenHaystackMainView: View {
|
||||
|
||||
Button(
|
||||
action: {
|
||||
if !self.mailPluginIsActive {
|
||||
if self.settingsUseMailPlugin && !self.mailPluginIsActive {
|
||||
self.showMailPlugInPopover.toggle()
|
||||
self.checkPluginIsRunning(silent: true, nil)
|
||||
} else {
|
||||
@@ -172,17 +177,26 @@ struct OpenHaystackMainView: View {
|
||||
return
|
||||
}
|
||||
|
||||
let pluginManager = MailPluginManager()
|
||||
|
||||
// Check if the plugin is installed
|
||||
if pluginManager.isMailPluginInstalled == false {
|
||||
// Install the mail plugin
|
||||
self.alertType = .activatePlugin
|
||||
self.checkPluginIsRunning(silent: true, nil)
|
||||
} else {
|
||||
self.checkPluginIsRunning(nil)
|
||||
/// Checks if the search party token was set in the settings. If true the plugin is also not needed
|
||||
if let tokenString = self.settingsSPToken {
|
||||
self.searchPartyToken = tokenString
|
||||
return
|
||||
}
|
||||
|
||||
/// Uses mail plugin if enabled in settings
|
||||
if self.settingsUseMailPlugin {
|
||||
let pluginManager = MailPluginManager()
|
||||
// Check if the plugin is installed
|
||||
if pluginManager.isMailPluginInstalled == false {
|
||||
// Install the mail plugin
|
||||
self.alertType = .activatePlugin
|
||||
self.checkPluginIsRunning(silent: true, nil)
|
||||
} else {
|
||||
self.checkPluginIsRunning(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// Download the location reports for all current accessories. Shows an error if something fails, like plug-in is missing
|
||||
@@ -306,7 +320,19 @@ struct OpenHaystackMainView: View {
|
||||
title: Text("Add the search party token"),
|
||||
message: Text(
|
||||
"""
|
||||
Please paste the search party token below after copying itfrom the macOS Keychain.
|
||||
Please paste the search party token in the settings after copying it from the macOS Keychain.
|
||||
The item that contains the key can be found by searching for:
|
||||
com.apple.account.DeviceLocator.search-party-token
|
||||
"""
|
||||
),
|
||||
dismissButton: Alert.Button.okay())
|
||||
case .invalidSearchPartyToken:
|
||||
return Alert(
|
||||
title: Text("Invalid search party token"),
|
||||
message: Text(
|
||||
"""
|
||||
The request returned an empty result, this is probably due to an invalid search party token.
|
||||
Please consider updating your search party token in the settings after copying it from the macOS Keychain.
|
||||
The item that contains the key can be found by searching for:
|
||||
com.apple.account.DeviceLocator.search-party-token
|
||||
"""
|
||||
@@ -317,6 +343,11 @@ struct OpenHaystackMainView: View {
|
||||
title: Text("Could not deploy"),
|
||||
message: Text("Deploying to microbit failed. Please reconnect the device over USB"),
|
||||
dismissButton: Alert.Button.okay())
|
||||
case .nrfDeployFailed:
|
||||
return Alert(
|
||||
title: Text("Could not deploy"),
|
||||
message: Text(self.scriptOutput ?? "Unknown Error"),
|
||||
dismissButton: Alert.Button.okay())
|
||||
case .deployedSuccessfully:
|
||||
return Alert(
|
||||
title: Text("Deploy successfull"),
|
||||
@@ -381,7 +412,9 @@ struct OpenHaystackMainView: View {
|
||||
|
||||
case keyError
|
||||
case searchPartyToken
|
||||
case invalidSearchPartyToken
|
||||
case deployFailed
|
||||
case nrfDeployFailed
|
||||
case deployedSuccessfully
|
||||
case deletionFailed
|
||||
case noReportsFound
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||||
//
|
||||
// Copyright © 2024 Secure Mobile Networking Lab (SEEMOO)
|
||||
// Copyright © 2024 The Open Wireless Link Project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct OpenHaystackSettingsView: View {
|
||||
var body: some View {
|
||||
TabView {
|
||||
GeneralSettingsView()
|
||||
.tabItem {
|
||||
Label("General", systemImage: "gear")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GeneralSettingsView: View {
|
||||
@AppStorage("useMailPlugin") private var useMailPlugin = false
|
||||
@AppStorage("searchPartyToken") private var searchPartyToken = ""
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Toggle("Use Apple Mail Plugin (only works on macOS 13 and lower)", isOn: $useMailPlugin)
|
||||
TextField("Search Party Token", text: $searchPartyToken)
|
||||
}
|
||||
.padding(20)
|
||||
.frame(width: 600, height: 200)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ struct OpenHaystackApp: App {
|
||||
var frameWidth: CGFloat? = nil
|
||||
var frameHeight: CGFloat? = nil
|
||||
|
||||
@State var checkedForUpdates = false
|
||||
|
||||
init() {
|
||||
let accessoryController: AccessoryController
|
||||
if ProcessInfo().arguments.contains("-preview") {
|
||||
@@ -35,9 +37,23 @@ struct OpenHaystackApp: App {
|
||||
OpenHaystackMainView()
|
||||
.environmentObject(self.accessoryController)
|
||||
.frame(width: self.frameWidth, height: self.frameHeight)
|
||||
.onAppear {
|
||||
self.checkForUpdates()
|
||||
}
|
||||
}
|
||||
.commands {
|
||||
SidebarCommands()
|
||||
}
|
||||
#if os(macOS)
|
||||
Settings {
|
||||
OpenHaystackSettingsView()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func checkForUpdates() {
|
||||
guard checkedForUpdates == false, ProcessInfo().arguments.contains("-stopUpdateCheck") == false else { return }
|
||||
UpdateCheckController.checkForNewVersion()
|
||||
checkedForUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,38 +111,160 @@
|
||||
<key>Supported12.1PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
</array>
|
||||
<key>Supported12.2PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
</array>
|
||||
<key>Supported12.3PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string># For Mail.app version 16.0 (3696.80.82.1.1) on macOS version 12.3.1 (build 21E258)</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported12.4PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported12.5PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported12.6PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported12.7PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported12.8PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported12.9PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported13.0PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported13.1PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported13.2PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported13.3PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported13.4PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported13.5PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported13.6PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported13.7PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported14.0PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported14.1PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
74
OpenHaystack/OpenHaystackTests/UpdateCheckTests.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||||
//
|
||||
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
|
||||
// Copyright © 2021 The Open Wireless Link Project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import OpenHaystack
|
||||
|
||||
class UpdateCheckTests: XCTestCase {
|
||||
|
||||
func testCompareVersions() {
|
||||
let i1 = "1.0.3"
|
||||
let a1 = "1.0.4"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a1, installedVersion: i1), .older)
|
||||
let a11 = "1.1"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a11, installedVersion: i1), .older)
|
||||
let a12 = "2"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a12, installedVersion: i1), .older)
|
||||
|
||||
let a2 = "1.0.3"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a2, installedVersion: i1), .same)
|
||||
|
||||
let a3 = "1.0.2"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a3, installedVersion: i1), .newer)
|
||||
let a31 = "1.0"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a31, installedVersion: i1), .newer)
|
||||
let a32 = "0.10.1"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a32, installedVersion: i1), .newer)
|
||||
|
||||
let a4 = "1.1.1"
|
||||
let i4 = "1.1.2"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a4, installedVersion: i4), .newer)
|
||||
let a41 = "1.0.2"
|
||||
XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a41, installedVersion: i1), .newer)
|
||||
}
|
||||
|
||||
func testHTMLVersionCompare() {
|
||||
let github =
|
||||
"""
|
||||
<h1 data-view-component="true" class="d-inline mr-3"><a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a></h1>
|
||||
<h1 data-view-component="true" class="d-inline mr-3"><a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a></h1>
|
||||
<a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a>
|
||||
"""
|
||||
|
||||
XCTAssertEqual(UpdateCheckController.getVersion(from: github), "0.4.1")
|
||||
|
||||
let h1 = "<h1>Release v0.4.1</h1> <h1>Release v0.3.1</h1>"
|
||||
XCTAssertEqual(UpdateCheckController.getVersion(from: h1), "0.4.1")
|
||||
let h2 = "<h1>Release v0.5</h1>"
|
||||
XCTAssertEqual(UpdateCheckController.getVersion(from: h2), "0.5")
|
||||
let h3 = "<h1>Release v1.5</h1>"
|
||||
XCTAssertEqual(UpdateCheckController.getVersion(from: h3), "1.5")
|
||||
let h4 = "<h1>Release v1</h1>"
|
||||
XCTAssertEqual(UpdateCheckController.getVersion(from: h4), "1")
|
||||
}
|
||||
|
||||
func testDownload() {
|
||||
let expect = expectation(description: "Update download")
|
||||
UpdateCheckController.downloadUpdate(
|
||||
version: "0.4.1",
|
||||
finished: { success in
|
||||
XCTAssertTrue(success)
|
||||
expect.fulfill()
|
||||
})
|
||||
wait(for: [expect], timeout: 20.0)
|
||||
|
||||
}
|
||||
}
|
||||
10
README.md
@@ -19,6 +19,7 @@ OpenHaystack is a framework for tracking personal Bluetooth devices via Apple's
|
||||
- [Finding](#finding-3)
|
||||
- [Searching](#searching-4)
|
||||
- [How to track other Bluetooth devices?](#how-to-track-other-bluetooth-devices)
|
||||
- [OpenHaystack Mobile](#openhaystack-mobile)
|
||||
- [Authors](#authors)
|
||||
- [References](#references)
|
||||
- [License](#license)
|
||||
@@ -116,6 +117,15 @@ Feel free to port OpenHaystack to other devices that support Bluetooth Low Energ
|
||||
|
||||

|
||||
|
||||
## OpenHaystack Mobile
|
||||
OpenHaystack Mobile is a complete reimplementation of the OpenHaystack macOS application for smartphones. The app provides the same functionality to create and track accessories and aims to increase the usability, especially for new users. In contrast to the macOS application, the location reports cannot be fetched directly on the smartphone, so the app requires a proxy server hosted on Mac hardware to access the Find My network. The proxy server can be accessed over a network by multiple users simultaneously.
|
||||
|
||||
To connect to your proxy server set the correct URL in: openhaystack-mobile/lib/findMy/reports_fetcher.dart
|
||||
|
||||
<img width="300" src="./Resources/mobile-map-view.png"> <img width="300" src="./Resources/mobile-accessory-history.png">
|
||||
|
||||
OpenHaystack Mobile is built with the cross-platform [Flutter framework](https://flutter.dev/) and currently runs on Android and iOS. More information about the app and usage instructions can be found in the [openhaystack-mobile](openhaystack-mobile) folder of this repository.
|
||||
|
||||
## Authors
|
||||
|
||||
- **Alexander Heinrich** ([@Sn0wfreezeDev](https://github.com/Sn0wfreezeDev), [email](mailto:aheinrich@seemoo.tu-darmstadt.de))
|
||||
|
||||
BIN
Resources/mobile-accessory-history.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
Resources/mobile-map-view.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
46
openhaystack-mobile/.gitignore
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
10
openhaystack-mobile/.metadata
Normal file
@@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 18116933e77adc82f80866c928266a5b4f1ed645
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
52
openhaystack-mobile/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# OpenHaystack Mobile
|
||||
Porting OpenHaystack to Mobile
|
||||
|
||||
# About OpenHaystack
|
||||
OpenHaystack is a project that allows location tracking of Bluetooth Low Energy (BLE) devices over Apples Find My Network.
|
||||
|
||||
# Development
|
||||
This project is written in [Dart](https://dart.dev/), using the cross platform development framework [Flutter](https://flutter.dev/). This allows the creation of apps for all major platforms using a single code base.
|
||||
|
||||
## Requisites
|
||||
To develop and build the project the following tools are needed and should be installed.
|
||||
|
||||
- [Flutter SDK](https://docs.flutter.dev/get-started/install)
|
||||
- [Xcode](https://developer.apple.com/xcode/) (for iOS)
|
||||
- [Android SDK / Studio](https://developer.android.com/studio/) (for Android)
|
||||
- (optional) IDE Plugin (e.g. for [VS Code](https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter))
|
||||
|
||||
To check the installation run `flutter doctor`. Before continuing review all displayed errors.
|
||||
|
||||
|
||||
## Getting Started
|
||||
First the necessary dependencies need to be installed. The IDE plugin may take care of this automatically.
|
||||
```bash
|
||||
$ flutter pub get
|
||||
```
|
||||
|
||||
Then set the location proxy server URL in [reports_fetcher.dart](lib/findMy/reports_fetcher.dart) (replace `https://add-your-proxy-server-here/getLocationReports` with your custom URL).
|
||||
|
||||
To run the debug version of the app start a supported emulator and run
|
||||
```bash
|
||||
$ flutter run
|
||||
```
|
||||
|
||||
When the app is running a new key pair can be created / imported in the app.
|
||||
|
||||
## Project Structure
|
||||
The project follows the default structure for flutter applications. The `android`, `ios` and `web` folders contain native projects for the specified platform. Native code can be added here for example to access special APIs.
|
||||
|
||||
The business logic and UI can be found in the `lib` folder. This folder is furthermore separated into modules containing code regarding a common aspect.
|
||||
The business logic for accessing and decrypting the location reports is separated in the `findMy` folder for easier reuse.
|
||||
|
||||
## Building
|
||||
This project currently supports iOS and Android targets.
|
||||
If you are building the project for the first time, you need to run
|
||||
```bash
|
||||
$ flutter pub run flutter_launcher_icons:main
|
||||
```
|
||||
to create the icons and then, to create a distributable application package run
|
||||
```bash
|
||||
$ flutter build [ios|apk|web]
|
||||
```
|
||||
The resulting build artifacts can be found in the `build` folder. To deploy the artifacts to a device consult the platform specific documentation.
|
||||
29
openhaystack-mobile/analysis_options.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
13
openhaystack-mobile/android/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
68
openhaystack-mobile/android/app/build.gradle
Normal file
@@ -0,0 +1,68 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "de.seemoo.android.openhaystack"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.seemoo.android.openhaystack">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||
|
||||
<queries>
|
||||
<!-- If your app opens https URLs -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<!-- If your app sends emails -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
63
openhaystack-mobile/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.seemoo.android.openhaystack">
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||
<application
|
||||
android:label="OpenHaystack"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<!-- Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/json" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<!-- If your app opens https URLs -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<!-- If your app sends emails -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,6 @@
|
||||
package de.seemoo.android.openhaystack
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,22 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.seemoo.android.openhaystack">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||
|
||||
<queries>
|
||||
<!-- If your app opens https URLs -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<!-- If your app sends emails -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
29
openhaystack-mobile/android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.0'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
openhaystack-mobile/android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
6
openhaystack-mobile/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||
11
openhaystack-mobile/android/settings.gradle
Normal file
@@ -0,0 +1,11 @@
|
||||
include ':app'
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
BIN
openhaystack-mobile/assets/OpenHaystackIcon.png
Normal file
|
After Width: | Height: | Size: 671 KiB |
34
openhaystack-mobile/ios/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
26
openhaystack-mobile/ios/Flutter/AppFrameworkInfo.plist
Normal 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>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>9.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
2
openhaystack-mobile/ios/Flutter/Debug.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
2
openhaystack-mobile/ios/Flutter/Release.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
41
openhaystack-mobile/ios/Podfile
Normal file
@@ -0,0 +1,41 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '9.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
||||
123
openhaystack-mobile/ios/Podfile.lock
Normal file
@@ -0,0 +1,123 @@
|
||||
PODS:
|
||||
- DKImagePickerController/Core (4.3.2):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.2)
|
||||
- DKImagePickerController/PhotoGallery (4.3.2):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.2)
|
||||
- DKPhotoGallery (0.0.17):
|
||||
- DKPhotoGallery/Core (= 0.0.17)
|
||||
- DKPhotoGallery/Model (= 0.0.17)
|
||||
- DKPhotoGallery/Preview (= 0.0.17)
|
||||
- DKPhotoGallery/Resource (= 0.0.17)
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Core (0.0.17):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Model (0.0.17):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Preview (0.0.17):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Resource (0.0.17):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_secure_storage (3.3.1):
|
||||
- Flutter
|
||||
- geocoding (1.0.5):
|
||||
- Flutter
|
||||
- location (0.0.1):
|
||||
- Flutter
|
||||
- maps_launcher (0.0.1):
|
||||
- Flutter
|
||||
- path_provider_ios (0.0.1):
|
||||
- Flutter
|
||||
- receive_sharing_intent (0.0.1):
|
||||
- Flutter
|
||||
- SDWebImage (5.12.3):
|
||||
- SDWebImage/Core (= 5.12.3)
|
||||
- SDWebImage/Core (5.12.3)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_ios (0.0.1):
|
||||
- Flutter
|
||||
- SwiftyGif (5.4.3)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- geocoding (from `.symlinks/plugins/geocoding/ios`)
|
||||
- location (from `.symlinks/plugins/location/ios`)
|
||||
- maps_launcher (from `.symlinks/plugins/maps_launcher/ios`)
|
||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_secure_storage:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
geocoding:
|
||||
:path: ".symlinks/plugins/geocoding/ios"
|
||||
location:
|
||||
:path: ".symlinks/plugins/location/ios"
|
||||
maps_launcher:
|
||||
:path: ".symlinks/plugins/maps_launcher/ios"
|
||||
path_provider_ios:
|
||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||
receive_sharing_intent:
|
||||
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_ios:
|
||||
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
|
||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
||||
geocoding: 32cfcdb16d38d907caaba65e2e42ad10d38bee58
|
||||
location: 3a2eed4dd2fab25e7b7baf2a9efefe82b512d740
|
||||
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203
|
||||
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
|
||||
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
|
||||
SDWebImage: 53179a2dba77246efa8a9b85f5c5b21f8f43e38f
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
shared_preferences_ios: aef470a42dc4675a1cdd50e3158b42e3d1232b32
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
|
||||
|
||||
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
785
openhaystack-mobile/ios/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,785 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
05B555C72796E0E100731D0C /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05B555C62796E0E100731D0C /* ShareViewController.swift */; };
|
||||
05B555CA2796E0E100731D0C /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 05B555C82796E0E100731D0C /* MainInterface.storyboard */; };
|
||||
05B555CE2796E0E100731D0C /* ShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 05B555C42796E0E100731D0C /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
FAFCFCF8207021C31CE2021E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30AF7E29CD9C08B4BA0A1C52 /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
05B555CC2796E0E100731D0C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 05B555C32796E0E100731D0C;
|
||||
remoteInfo = ShareExtension;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
05B555CF2796E0E100731D0C /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
05B555CE2796E0E100731D0C /* ShareExtension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
05B555C42796E0E100731D0C /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
05B555C62796E0E100731D0C /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
05B555C92796E0E100731D0C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
05B555CB2796E0E100731D0C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
05B555D42796E21E00731D0C /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
05B555D52796E25F00731D0C /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
30AF7E29CD9C08B4BA0A1C52 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
5147928FEB8FF70E5DCF0B91 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
C142B296C6D81AB3420C4869 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D67EF54705446F3A326E5778 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
05B555C12796E0E100731D0C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FAFCFCF8207021C31CE2021E /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
05B555C52796E0E100731D0C /* ShareExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05B555D52796E25F00731D0C /* ShareExtension.entitlements */,
|
||||
05B555C62796E0E100731D0C /* ShareViewController.swift */,
|
||||
05B555C82796E0E100731D0C /* MainInterface.storyboard */,
|
||||
05B555CB2796E0E100731D0C /* Info.plist */,
|
||||
);
|
||||
path = ShareExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
67FFEEB1C00E19A4B34373A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30AF7E29CD9C08B4BA0A1C52 /* Pods_Runner.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6BCC37388A6BAAA8424A31B1 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5147928FEB8FF70E5DCF0B91 /* Pods-Runner.debug.xcconfig */,
|
||||
C142B296C6D81AB3420C4869 /* Pods-Runner.release.xcconfig */,
|
||||
D67EF54705446F3A326E5778 /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
05B555C52796E0E100731D0C /* ShareExtension */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
6BCC37388A6BAAA8424A31B1 /* Pods */,
|
||||
67FFEEB1C00E19A4B34373A0 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
05B555C42796E0E100731D0C /* ShareExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05B555D42796E21E00731D0C /* Runner.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
05B555C32796E0E100731D0C /* ShareExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 05B555D32796E0E100731D0C /* Build configuration list for PBXNativeTarget "ShareExtension" */;
|
||||
buildPhases = (
|
||||
05B555C02796E0E100731D0C /* Sources */,
|
||||
05B555C12796E0E100731D0C /* Frameworks */,
|
||||
05B555C22796E0E100731D0C /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = ShareExtension;
|
||||
productName = ShareExtension;
|
||||
productReference = 05B555C42796E0E100731D0C /* ShareExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
F8ED8338B5331552C3B3682F /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
090062C30368FBD0ED95CAB1 /* [CP] Embed Pods Frameworks */,
|
||||
05B555CF2796E0E100731D0C /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
05B555CD2796E0E100731D0C /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastUpgradeCheck = 1300;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
05B555C32796E0E100731D0C = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
05B555C32796E0E100731D0C /* ShareExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
05B555C22796E0E100731D0C /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
05B555CA2796E0E100731D0C /* MainInterface.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
090062C30368FBD0ED95CAB1 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
F8ED8338B5331552C3B3682F /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
05B555C02796E0E100731D0C /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
05B555C72796E0E100731D0C /* ShareViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
05B555CD2796E0E100731D0C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 05B555C32796E0E100731D0C /* ShareExtension */;
|
||||
targetProxy = 05B555CC2796E0E100731D0C /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
05B555C82796E0E100731D0C /* MainInterface.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
05B555C92796E0E100731D0C /* Base */,
|
||||
);
|
||||
name = MainInterface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
05B555D02796E0E100731D0C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = H9XHQ4WHSF;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.seemoo.ios.openhaystack.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
05B555D12796E0E100731D0C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = H9XHQ4WHSF;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.seemoo.ios.openhaystack.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
05B555D22796E0E100731D0C /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = H9XHQ4WHSF;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.seemoo.ios.openhaystack.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = H9XHQ4WHSF;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.seemoo.ios.openhaystack;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = H9XHQ4WHSF;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.seemoo.ios.openhaystack;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = H9XHQ4WHSF;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.seemoo.ios.openhaystack;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
05B555D32796E0E100731D0C /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
05B555D02796E0E100731D0C /* Debug */,
|
||||
05B555D12796E0E100731D0C /* Release */,
|
||||
05B555D22796E0E100731D0C /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
7
openhaystack-mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
10
openhaystack-mobile/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
13
openhaystack-mobile/ios/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 530 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 21 KiB |
23
openhaystack-mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
openhaystack-mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
openhaystack-mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
openhaystack-mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
openhaystack-mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
26
openhaystack-mobile/ios/Runner/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
63
openhaystack-mobile/ios/Runner/Info.plist
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>OpenHaystack</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>ShareMedia</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict/>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>https</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Location is needed to show the users location (optional)</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
1
openhaystack-mobile/ios/Runner/Runner-Bridging-Header.h
Normal file
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||