mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-10 23:33:29 +01:00
Compare commits
27 Commits
auto-updat
...
103-add-ht
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b977092f5 | ||
|
|
c6915d1f91 | ||
|
|
d6ea725329 | ||
|
|
79d677cf3c | ||
|
|
be39b6512f | ||
|
|
fcfeea35da | ||
|
|
7d0eb8c61e | ||
|
|
4d8438a6b6 | ||
|
|
f611244e35 | ||
|
|
546a978d3b | ||
|
|
70b23fb073 | ||
|
|
a56ca597d6 | ||
|
|
679e0228a8 | ||
|
|
e153394323 | ||
|
|
5bd1fcfcfd | ||
|
|
2a392ddc44 | ||
|
|
b5cb8bc0d9 | ||
|
|
ab9df8201a | ||
|
|
b0fac34ffc | ||
|
|
5ede9f7c6b | ||
|
|
c7254fd23e | ||
|
|
55fcea04af | ||
|
|
c212c0a6b2 | ||
|
|
a31fd6709a | ||
|
|
e367fd2b73 | ||
|
|
1ca67d0241 | ||
|
|
8ffa952ff9 |
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Logs**
|
||||
Please send logs generated with [idevicedebug](https://github.com/libimobiledevice/libimobiledevice) or Xcode. We will close the issue if a log is missing.
|
||||
|
||||
**iDevice (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Bug Report
|
||||
description: Report a bug
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
assignees:
|
||||
- naturecodevoid
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Before you continue filling out the report, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the bug you are experiencing** in case it has already been reported.
|
||||
|
||||
**Please use [Discord](https://discord.gg/RgpFBX3Q3k) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: What is the bug and how did you discover it?
|
||||
placeholder: Please be clear and concise with your description.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: how-to-reproduce
|
||||
attributes:
|
||||
label: Instructions to reproduce
|
||||
description: Please include clear and consistent instructions for reproducing the bug to make it easier for us to fix it.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: app-version
|
||||
attributes:
|
||||
label: What version of SideStore are you using?
|
||||
description: To retrieve this, go to `Settings` in the SideStore app and scroll down to the bottom.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: other-info
|
||||
attributes:
|
||||
label: Other info
|
||||
description: If you have any other comments, other info that might be useful, or if you found a workaround, please put it here.
|
||||
10
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
10
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# force issue template usage
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Discord
|
||||
url: https://discord.gg/RgpFBX3Q3k
|
||||
about: If you need support, please go here first instead of making an issue!
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/SideStore/SideStore/discussions
|
||||
about: As an alternative to Discord, you can also make a new GitHub discussion.
|
||||
33
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
33
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Feature Request
|
||||
description: Suggest a feature
|
||||
title: "[FEATURE REQUEST] "
|
||||
labels: ["enhancement"]
|
||||
assignees:
|
||||
- naturecodevoid
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request! Before you continue filling out the form, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the feature you are suggestion** in case it has already been suggested.
|
||||
|
||||
**Please use [Discord](https://discord.gg/RgpFBX3Q3k) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: What is the feature? How would it work?
|
||||
placeholder: Please be clear and concise with your description.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: use-cases
|
||||
attributes:
|
||||
label: Use cases
|
||||
description: Please include multiple use cases where this feature would be useful.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: If you have alternative ideas of how this feature could work, you can put them here.
|
||||
15
.github/pull_request_template.md
vendored
Normal file
15
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
### Changes
|
||||
|
||||
<!-- Fill this list with what your PR changes. Example: -->
|
||||
- Fix bug
|
||||
- Change UI for QOL
|
||||
|
||||
<!-- If your PR is ready to be merged, you can remove this section. -->
|
||||
### Todo before merge
|
||||
|
||||
<!-- Example: -->
|
||||
- [x] Finish UI changes
|
||||
- [ ] Test
|
||||
|
||||
<!-- If your PR doesn't close an issue, you can remove the next line. -->
|
||||
Closes #1234
|
||||
3
.github/workflows/pr.yml
vendored
3
.github/workflows/pr.yml
vendored
@@ -74,6 +74,9 @@ jobs:
|
||||
# - name: Build minimuxer
|
||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
- name: Add PR suffix to version
|
||||
run: sed -e '/MARKETING_VERSION = .*/s/$/-pr.${{ github.event.pull_request.number }}/' -i '' Build.xcconfig
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
with:
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -21,7 +21,7 @@
|
||||
url = https://github.com/libimobiledevice/libimobiledevice-glue
|
||||
[submodule "Dependencies/minimuxer"]
|
||||
path = Dependencies/minimuxer
|
||||
url = https://github.com/jkcoxson/minimuxer
|
||||
url = https://github.com/SideStore/minimuxer
|
||||
[submodule "Dependencies/libfragmentzip"]
|
||||
path = Dependencies/libfragmentzip
|
||||
url = https://github.com/SideStore/libfragmentzip.git
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; };
|
||||
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
||||
99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 99C4EF4C2979132100CB538D /* SemanticVersion */; };
|
||||
B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; };
|
||||
B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B33FFBA8295F8E98002259E6 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B343F894295F7F9B002B1159 /* libfragmentzip.a */; };
|
||||
@@ -42,6 +43,7 @@
|
||||
B39575F5284F29E20080B4FF /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B39575F4284F29E20080B4FF /* Roxas.framework */; };
|
||||
B39F16132918D7C5002E9404 /* Consts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39F16122918D7C5002E9404 /* Consts.swift */; };
|
||||
B39F16152918D7DA002E9404 /* Consts+Proxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39F16142918D7DA002E9404 /* Consts+Proxy.swift */; };
|
||||
B3C053B7295F921E0079DB81 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C053B6295F921E0079DB81 /* NetworkService.swift */; };
|
||||
B3C395F1284F2DE700DA9E2F /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = B3C395F0284F2DE700DA9E2F /* KeychainAccess */; };
|
||||
B3C395F4284F35DD00DA9E2F /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = B3C395F3284F35DD00DA9E2F /* Nuke */; };
|
||||
B3C395F7284F362400DA9E2F /* AppCenterAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = B3C395F6284F362400DA9E2F /* AppCenterAnalytics */; };
|
||||
@@ -532,6 +534,7 @@
|
||||
B39575F4284F29E20080B4FF /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B39F16122918D7C5002E9404 /* Consts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consts.swift; sourceTree = "<group>"; };
|
||||
B39F16142918D7DA002E9404 /* Consts+Proxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Consts+Proxy.swift"; sourceTree = "<group>"; };
|
||||
B3C053B6295F921E0079DB81 /* NetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
|
||||
B3C39606284F4C8400DA9E2F /* CodeSigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CodeSigning.xcconfig; sourceTree = "<group>"; };
|
||||
B3C39607284F4C8400DA9E2F /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = "<group>"; };
|
||||
B3C39608284F4C8400DA9E2F /* CodeSigning.xcconfig.sample */ = {isa = PBXFileReference; lastKnownFileType = text; path = CodeSigning.xcconfig.sample; sourceTree = "<group>"; };
|
||||
@@ -896,6 +899,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B3C395F1284F2DE700DA9E2F /* KeychainAccess in Frameworks */,
|
||||
99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */,
|
||||
4879A95F2861046500FC1BBD /* AltSign in Frameworks */,
|
||||
B39575F5284F29E20080B4FF /* Roxas.framework in Frameworks */,
|
||||
);
|
||||
@@ -1024,6 +1028,14 @@
|
||||
path = Consts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B3C053B5295F921E0079DB81 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B3C053B6295F921E0079DB81 /* NetworkService.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF0DCA642433BDE200E3A595 /* Analytics */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1558,6 +1570,7 @@
|
||||
BF3D648922E79A7700E9056B /* Types */,
|
||||
BFD2479D2284FBBD00981D42 /* Extensions */,
|
||||
BFD247962284D7C100981D42 /* Resources */,
|
||||
B3C053B5295F921E0079DB81 /* Services */,
|
||||
BF6C8FA8242935CA00125131 /* Dependencies */,
|
||||
BFD247972284D7D800981D42 /* Supporting Files */,
|
||||
1920B04E2924AC8300744F60 /* Settings.bundle */,
|
||||
@@ -1937,11 +1950,13 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
99C4EF51297994E200CB538D /* PBXTargetDependency */,
|
||||
);
|
||||
name = AltStoreCore;
|
||||
packageProductDependencies = (
|
||||
B3C395F0284F2DE700DA9E2F /* KeychainAccess */,
|
||||
4879A95E2861046500FC1BBD /* AltSign */,
|
||||
99C4EF4C2979132100CB538D /* SemanticVersion */,
|
||||
);
|
||||
productName = AltStoreCore;
|
||||
productReference = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */;
|
||||
@@ -2060,6 +2075,7 @@
|
||||
B3C395FD284F3C0900DA9E2F /* XCRemoteSwiftPackageReference "STPrivilegedTask" */,
|
||||
4879A95D2861046500FC1BBD /* XCRemoteSwiftPackageReference "AltSign" */,
|
||||
4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */,
|
||||
99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */,
|
||||
);
|
||||
productRefGroup = BFD2476B2284B9A500981D42 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -2468,6 +2484,7 @@
|
||||
BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */,
|
||||
B39F16152918D7DA002E9404 /* Consts+Proxy.swift in Sources */,
|
||||
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */,
|
||||
B3C053B7295F921E0079DB81 /* NetworkService.swift in Sources */,
|
||||
BF6F439223644C6E00A0B879 /* RefreshAltStoreViewController.swift in Sources */,
|
||||
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */,
|
||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
|
||||
@@ -2543,6 +2560,10 @@
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 191E5FD9290AFA49001A3B7C /* OpenSSL */;
|
||||
};
|
||||
99C4EF51297994E200CB538D /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 99C4EF50297994E200CB538D /* SemanticVersion */;
|
||||
};
|
||||
B343F86F295F76FD002B1159 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = "minimuxer-staticlib";
|
||||
@@ -3346,6 +3367,14 @@
|
||||
minimumVersion = 1.1.180;
|
||||
};
|
||||
};
|
||||
99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SwiftPackageIndex/SemanticVersion.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.3.5;
|
||||
};
|
||||
};
|
||||
B3C395EF284F2DE700DA9E2F /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git";
|
||||
@@ -3417,6 +3446,16 @@
|
||||
package = 4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */;
|
||||
productName = OpenSSL;
|
||||
};
|
||||
99C4EF4C2979132100CB538D /* SemanticVersion */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */;
|
||||
productName = SemanticVersion;
|
||||
};
|
||||
99C4EF50297994E200CB538D /* SemanticVersion */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */;
|
||||
productName = SemanticVersion;
|
||||
};
|
||||
B3C395F0284F2DE700DA9E2F /* KeychainAccess */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = B3C395EF284F2DE700DA9E2F /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||
|
||||
@@ -63,6 +63,15 @@
|
||||
"version" : "1.10.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "semanticversion",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
|
||||
"state" : {
|
||||
"revision" : "fc670910dc0903cc269b3d0b776cda5703979c4e",
|
||||
"version" : "0.3.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sparkle",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
@@ -754,7 +754,7 @@ private extension MyAppsViewController
|
||||
{
|
||||
let downloadProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
downloadOperation = RSTAsyncBlockOperation { (operation) in
|
||||
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
|
||||
let downloadTask = AppServices.network.session.downloadTask(with: url) { (fileURL, response, error) in
|
||||
do
|
||||
{
|
||||
let (fileURL, _) = try Result((fileURL, response), error).get()
|
||||
|
||||
@@ -39,8 +39,8 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
private var sourceURL: URL?
|
||||
private let destinationURL: URL
|
||||
|
||||
private let session = URLSession(configuration: .default)
|
||||
private let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
|
||||
private let session: URLSession = AppServices.network.backgroundSession
|
||||
private let temporaryDirectory: URL = FileManager.default.uniqueTemporaryURL()
|
||||
|
||||
init(app: AppProtocol, destinationURL: URL, context: AppOperationContext)
|
||||
{
|
||||
|
||||
@@ -34,30 +34,31 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
||||
|
||||
let url = AnisetteManager.currentURL
|
||||
DLOG("Anisette URL: %@", url.absoluteString)
|
||||
|
||||
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
guard let data = data, error == nil else { return }
|
||||
|
||||
do {
|
||||
// make sure this JSON is in the format we expect
|
||||
// convert data to json
|
||||
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
|
||||
// try to read out a dictionary
|
||||
//for some reason serial number isn't needed but it doesn't work unless it has a value
|
||||
let formattedJSON: [String: String] = ["machineID": json["X-Apple-I-MD-M"]!, "oneTimePassword": json["X-Apple-I-MD"]!, "localUserID": json["X-Apple-I-MD-LU"]!, "routingInfo": json["X-Apple-I-MD-RINFO"]!, "deviceUniqueIdentifier": json["X-Mme-Device-Id"]!, "deviceDescription": json["X-MMe-Client-Info"]!, "date": json["X-Apple-I-Client-Time"]!, "locale": json["X-Apple-Locale"]!, "timeZone": json["X-Apple-I-TimeZone"]!, "deviceSerialNumber": "1"]
|
||||
|
||||
if let anisette = ALTAnisetteData(json: formattedJSON) {
|
||||
|
||||
let task = AppServices.network.session.dataTask(with: url) { data, response, error in
|
||||
|
||||
guard let data = data, error == nil else { return }
|
||||
|
||||
do {
|
||||
// make sure this JSON is in the format we expect
|
||||
// convert data to json
|
||||
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
|
||||
// try to read out a dictionary
|
||||
//for some reason serial number isn't needed but it doesn't work unless it has a value
|
||||
let formattedJSON: [String: String] = ["machineID": json["X-Apple-I-MD-M"]!, "oneTimePassword": json["X-Apple-I-MD"]!, "localUserID": json["X-Apple-I-MD-LU"]!, "routingInfo": json["X-Apple-I-MD-RINFO"]!, "deviceUniqueIdentifier": json["X-Mme-Device-Id"]!, "deviceDescription": json["X-MMe-Client-Info"]!, "date": json["X-Apple-I-Client-Time"]!, "locale": json["X-Apple-Locale"]!, "timeZone": json["X-Apple-I-TimeZone"]!, "deviceSerialNumber": "1"]
|
||||
|
||||
if let anisette = ALTAnisetteData(json: formattedJSON) {
|
||||
DLOG("Anisette used: %@", formattedJSON)
|
||||
self.finish(.success(anisette))
|
||||
}
|
||||
}
|
||||
} catch let error as NSError {
|
||||
print("Failed to load: \(error.localizedDescription)")
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task.resume()
|
||||
self.finish(.success(anisette))
|
||||
}
|
||||
}
|
||||
} catch let error as NSError {
|
||||
print("Failed to load: \(error.localizedDescription)")
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,51 @@ import CoreData
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
func matches(for regex: String, in text: String) -> [String] {
|
||||
|
||||
do {
|
||||
let regex = try NSRegularExpression(pattern: regex)
|
||||
let results = regex.matches(in: text,
|
||||
range: NSRange(text.startIndex..., in: text))
|
||||
return results.map {
|
||||
String(text[Range($0.range, in: text)!])
|
||||
}
|
||||
} catch let error {
|
||||
print("invalid regex: \(error.localizedDescription)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func containsRedirect(_ response: URLResponse?, data: Data?) -> String? {
|
||||
if let httpResponse = response as? HTTPURLResponse {
|
||||
print("Request status code: \(httpResponse.statusCode)")
|
||||
print("Request headers: \(httpResponse.allHeaderFields.debugDescription)")
|
||||
|
||||
guard let data = data else {
|
||||
print("Request error: missing data")
|
||||
return nil
|
||||
}
|
||||
let rawHttp = String(decoding: data, as: UTF8.self)
|
||||
let regex = "url=((https|http):\\/\\/[\\S]*)\">"
|
||||
guard var redirectURL = matches(for: regex, in: rawHttp).first else {
|
||||
return nil
|
||||
}
|
||||
redirectURL = redirectURL.replacingOccurrences(of: "url=", with: "")
|
||||
redirectURL = redirectURL.replacingOccurrences(of: "\">", with: "")
|
||||
print("redirectURL: \(redirectURL)")
|
||||
return redirectURL
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc(FetchSourceOperation)
|
||||
final class FetchSourceOperation: ResultOperation<Source>
|
||||
{
|
||||
let sourceURL: URL
|
||||
let managedObjectContext: NSManagedObjectContext
|
||||
|
||||
private let session: URLSession
|
||||
private let session: URLSession = AppServices.network.sessionNoCache
|
||||
|
||||
private lazy var dateFormatter: ISO8601DateFormatter = {
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
@@ -29,79 +67,98 @@ final class FetchSourceOperation: ResultOperation<Source>
|
||||
{
|
||||
self.sourceURL = sourceURL
|
||||
self.managedObjectContext = managedObjectContext
|
||||
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||
configuration.urlCache = nil
|
||||
|
||||
self.session = URLSession(configuration: configuration)
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
let dataTask = self.session.dataTask(with: self.sourceURL) { (data, response, error) in
|
||||
|
||||
let childContext = DatabaseManager.shared.persistentContainer.newBackgroundContext(withParent: self.managedObjectContext)
|
||||
childContext.mergePolicy = NSOverwriteMergePolicy
|
||||
childContext.perform {
|
||||
do
|
||||
{
|
||||
let (data, _) = try Result((data, response), error).get()
|
||||
|
||||
let decoder = AltStoreCore.JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
|
||||
let container = try decoder.singleValueContainer()
|
||||
let text = try container.decode(String.self)
|
||||
|
||||
// Full ISO8601 Format.
|
||||
self.dateFormatter.formatOptions = [.withFullDate, .withFullTime, .withTimeZone]
|
||||
if let date = self.dateFormatter.date(from: text)
|
||||
{
|
||||
return date
|
||||
}
|
||||
|
||||
// Just date portion of ISO8601.
|
||||
self.dateFormatter.formatOptions = [.withFullDate]
|
||||
if let date = self.dateFormatter.date(from: text)
|
||||
{
|
||||
return date
|
||||
}
|
||||
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date is in invalid format.")
|
||||
})
|
||||
|
||||
decoder.managedObjectContext = childContext
|
||||
decoder.sourceURL = self.sourceURL
|
||||
|
||||
let source = try decoder.decode(Source.self, from: data)
|
||||
let identifier = source.identifier
|
||||
|
||||
try childContext.save()
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
if let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), identifier), in: self.managedObjectContext)
|
||||
{
|
||||
self.finish(.success(source))
|
||||
}
|
||||
else
|
||||
{
|
||||
self.finish(.failure(OperationError.noSources))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.managedObjectContext.perform {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadSource(self.sourceURL)
|
||||
}
|
||||
|
||||
private func loadSource(_ url: URL) {
|
||||
let dataTask = createDataTask(with: url)
|
||||
self.progress.addChild(dataTask.progress, withPendingUnitCount: 1)
|
||||
|
||||
dataTask.resume()
|
||||
}
|
||||
|
||||
private func createDataTask(with url: URL) -> URLSessionDataTask {
|
||||
let dataTask = self.session.dataTask(with: url) { (data, response, error) in
|
||||
// Test code for http redirect HTML, though seems I got jekyll/sidestore to work without this now - @JoeMatt
|
||||
if let error = error {
|
||||
print("Request error: \(error)")
|
||||
// TODO: Handle error
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
if let redirect = containsRedirect(response, data: data), let redirectURL = URL(string: redirect) {
|
||||
DispatchQueue.main.async {
|
||||
self.loadSource(redirectURL)
|
||||
}
|
||||
} else {
|
||||
self.processJSON(data: data, response: response, error: error)
|
||||
}
|
||||
}
|
||||
return dataTask
|
||||
}
|
||||
|
||||
private func processJSON(data: Data?, response: URLResponse?, error: Error?) {
|
||||
let childContext = DatabaseManager.shared.persistentContainer.newBackgroundContext(withParent: self.managedObjectContext)
|
||||
childContext.mergePolicy = NSOverwriteMergePolicy
|
||||
childContext.perform {
|
||||
do
|
||||
{
|
||||
let (data, _) = try Result((data, response), error).get()
|
||||
|
||||
let decoder = AltStoreCore.JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
|
||||
let container = try decoder.singleValueContainer()
|
||||
let text = try container.decode(String.self)
|
||||
|
||||
// Full ISO8601 Format.
|
||||
self.dateFormatter.formatOptions = [.withFullDate, .withFullTime, .withTimeZone]
|
||||
if let date = self.dateFormatter.date(from: text)
|
||||
{
|
||||
return date
|
||||
}
|
||||
|
||||
// Just date portion of ISO8601.
|
||||
self.dateFormatter.formatOptions = [.withFullDate]
|
||||
if let date = self.dateFormatter.date(from: text)
|
||||
{
|
||||
return date
|
||||
}
|
||||
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date is in invalid format.")
|
||||
})
|
||||
|
||||
decoder.managedObjectContext = childContext
|
||||
// Note: This may need to be response.url instead, to handle redirects @JoeMatt
|
||||
decoder.sourceURL = response?.url ?? self.sourceURL
|
||||
|
||||
let source = try decoder.decode(Source.self, from: data)
|
||||
let identifier = source.identifier
|
||||
|
||||
try childContext.save()
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
if let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), identifier), in: self.managedObjectContext)
|
||||
{
|
||||
self.finish(.success(source))
|
||||
}
|
||||
else
|
||||
{
|
||||
self.finish(.failure(OperationError.noSources))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.managedObjectContext.perform {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ final class FetchTrustedSourcesOperation: ResultOperation<[FetchTrustedSourcesOp
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
let dataTask = URLSession.shared.dataTask(with: .trustedSources) { (data, response, error) in
|
||||
let session: URLSession = AppServices.network.session
|
||||
let dataTask = session.dataTask(with: .trustedSources) { (data, response, error) in
|
||||
do
|
||||
{
|
||||
if let response = response as? HTTPURLResponse
|
||||
|
||||
@@ -43,7 +43,7 @@ final class UpdatePatronsOperation: ResultOperation<Void>
|
||||
{
|
||||
super.main()
|
||||
|
||||
let dataTask = URLSession.shared.dataTask(with: .patreonInfo) { (data, response, error) in
|
||||
let dataTask = AppServices.network.session.dataTask(with: .patreonInfo) { (data, response, error) in
|
||||
do
|
||||
{
|
||||
if let response = response as? HTTPURLResponse
|
||||
|
||||
94
AltStore/Services/NetworkService.swift
Normal file
94
AltStore/Services/NetworkService.swift
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// NetworkService.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Joseph Mattiello on 11/6/22.
|
||||
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol Services {
|
||||
var network: any NetworkService { get }
|
||||
}
|
||||
|
||||
public protocol NetworkService {
|
||||
var session: URLSession { get }
|
||||
var sessionNoCache: URLSession { get }
|
||||
var backgroundSession: URLSession { get }
|
||||
}
|
||||
|
||||
public struct DefaultServices: Services {
|
||||
public var network: NetworkService = AltNetworkService()
|
||||
}
|
||||
|
||||
let AppServices = DefaultServices()
|
||||
|
||||
final public class AltNetworkDelegate: NSObject, URLSessionDelegate, URLSessionTaskDelegate {
|
||||
public override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public struct Options: OptionSet {
|
||||
public let rawValue: Int
|
||||
public init(rawValue: Int) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let redirect = Options(rawValue: 1 << 0)
|
||||
public static let all: Options = [.redirect]
|
||||
}
|
||||
|
||||
var options: Options = .all
|
||||
|
||||
// public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
|
||||
// if options.contains(.redirect) {
|
||||
// completionHandler(request)
|
||||
// } else {
|
||||
// completionHandler(nil)
|
||||
// }
|
||||
// }
|
||||
|
||||
public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest) async -> URLRequest? {
|
||||
if options.contains(.redirect) {
|
||||
return request
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class AltNetworkService: NetworkService {
|
||||
let delegate = AltNetworkDelegate()
|
||||
|
||||
lazy var delegateQueue: OperationQueue = {
|
||||
let queue = OperationQueue.init()
|
||||
queue.name = "com.sidestore.NetworkService.serialOperationQueue"
|
||||
queue.maxConcurrentOperationCount = 1
|
||||
return queue
|
||||
}()
|
||||
|
||||
public lazy var session: URLSession = {
|
||||
let configuration: URLSessionConfiguration = URLSessionConfiguration.default
|
||||
configuration.httpShouldSetCookies = true
|
||||
configuration.httpShouldUsePipelining = true
|
||||
let session = URLSession.init(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)
|
||||
return session
|
||||
}()
|
||||
|
||||
public lazy var sessionNoCache: URLSession = {
|
||||
let configuration: URLSessionConfiguration = URLSessionConfiguration.default
|
||||
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||
configuration.urlCache = nil
|
||||
let session = URLSession.init(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)
|
||||
return session
|
||||
}()
|
||||
|
||||
static let backgroundSessionIdentifier = "SideStoreBackgroundSession"
|
||||
|
||||
public lazy var backgroundSession: URLSession = {
|
||||
let configuration: URLSessionConfiguration = URLSessionConfiguration.background(withIdentifier: AltNetworkService.backgroundSessionIdentifier)
|
||||
let session = URLSession.init(configuration: configuration, delegate: delegate, delegateQueue: nil)
|
||||
return session
|
||||
}()
|
||||
}
|
||||
@@ -16,9 +16,10 @@
|
||||
<key>Key</key>
|
||||
<string>customAnisetteURL</string>
|
||||
<key>DefaultValue</key>
|
||||
<string>http://191.101.206.188:6969</string>
|
||||
<string>http://ani.sidestore.io</string>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>SideStore</string>
|
||||
<string>Macley (US)</string>
|
||||
<string>Macley (DE)</string>
|
||||
<string>DrPudding</string>
|
||||
@@ -31,6 +32,7 @@
|
||||
</array>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<string>http://ani.sidestore.io</string>
|
||||
<string>http://us1.sternserv.tech</string>
|
||||
<string>http://de1.sternserv.tech</string>
|
||||
<string>https://sign.rheaa.xyz</string>
|
||||
|
||||
@@ -173,8 +173,7 @@ private extension PatreonViewController
|
||||
|
||||
@objc func openPatreonURL(_ sender: UIButton)
|
||||
{
|
||||
// TODO: Is this the final URL? @JoeMatt
|
||||
let patreonURL = URL(string: "https://www.patreon.com/JitStreamer")!
|
||||
let patreonURL = URL(string: "https://www.patreon.com/SideStore")!
|
||||
|
||||
let safariViewController = SFSafariViewController(url: patreonURL)
|
||||
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
<attribute name="certificateSerialNumber" optional="YES" attributeType="String"/>
|
||||
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hasAlternateIcon" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="hasUpdate" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="installedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="isActive" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="isRefreshing" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import Foundation
|
||||
import CoreData
|
||||
|
||||
import AltSign
|
||||
import SemanticVersion
|
||||
|
||||
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
||||
public let ALTActiveAppsLimit = 3
|
||||
@@ -59,6 +60,21 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol
|
||||
return self.storeApp == nil
|
||||
}
|
||||
|
||||
@objc public var hasUpdate: Bool {
|
||||
if self.storeApp == nil { return false }
|
||||
if self.storeApp!.latestVersion == nil { return false }
|
||||
|
||||
let currentVersion = SemanticVersion(self.version)
|
||||
let latestVersion = SemanticVersion(self.storeApp!.latestVersion!.version)
|
||||
|
||||
if currentVersion == nil || latestVersion == nil {
|
||||
// One of the versions is not valid SemVer, fall back to comparing the version strings by character
|
||||
return self.version < self.storeApp!.latestVersion!.version
|
||||
}
|
||||
|
||||
return currentVersion! < latestVersion!
|
||||
}
|
||||
|
||||
public var appIDCount: Int {
|
||||
return 1 + self.appExtensions.count
|
||||
}
|
||||
@@ -147,8 +163,8 @@ public extension InstalledApp
|
||||
class func updatesFetchRequest() -> NSFetchRequest<InstalledApp>
|
||||
{
|
||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K",
|
||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestVersion.version))
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K == YES",
|
||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.hasUpdate))
|
||||
return fetchRequest
|
||||
}
|
||||
|
||||
|
||||
2
Dependencies/minimuxer
vendored
2
Dependencies/minimuxer
vendored
Submodule Dependencies/minimuxer updated: 87fda7dd92...6a5a5b4e73
Reference in New Issue
Block a user