Compare commits
142 Commits
0.1.1
...
103-add-ht
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b977092f5 | ||
|
|
c6915d1f91 | ||
|
|
d6ea725329 | ||
|
|
79d677cf3c | ||
|
|
be39b6512f | ||
|
|
fcfeea35da | ||
|
|
7d0eb8c61e | ||
|
|
4d8438a6b6 | ||
|
|
f611244e35 | ||
|
|
546a978d3b | ||
|
|
70b23fb073 | ||
|
|
a56ca597d6 | ||
|
|
679e0228a8 | ||
|
|
e153394323 | ||
|
|
5bd1fcfcfd | ||
|
|
2a392ddc44 | ||
|
|
b5cb8bc0d9 | ||
|
|
fa170bcf98 | ||
|
|
7939d46949 | ||
|
|
ab9df8201a | ||
|
|
4a670ec091 | ||
|
|
10e57e59c4 | ||
|
|
b9ec43ef34 | ||
|
|
42197cd375 | ||
|
|
704852973b | ||
|
|
056b4200df | ||
|
|
250a7d8627 | ||
|
|
1ba51e161e | ||
|
|
32e58af896 | ||
|
|
312fa6fe76 | ||
|
|
afbe0837ba | ||
|
|
36ad2a720f | ||
|
|
901e3b14bb | ||
|
|
588d209f7b | ||
|
|
554c54e6be | ||
|
|
b0fac34ffc | ||
|
|
5ede9f7c6b | ||
|
|
c7254fd23e | ||
|
|
55fcea04af | ||
|
|
c212c0a6b2 | ||
|
|
a31fd6709a | ||
|
|
e367fd2b73 | ||
|
|
1ca67d0241 | ||
|
|
8ffa952ff9 | ||
|
|
da246fa30b | ||
|
|
13f306742e | ||
|
|
f3815dc45e | ||
|
|
d086254012 | ||
|
|
bc4d5ba097 | ||
|
|
c556783fe3 | ||
|
|
5fba4c12aa | ||
|
|
7e0dde3ece | ||
|
|
fc03e83531 | ||
|
|
4c441077c7 | ||
|
|
4a5ca81e9a | ||
|
|
75eebe8f8c | ||
|
|
271a8cdac5 | ||
|
|
25103c1188 | ||
|
|
d81058e606 | ||
|
|
693df54b3b | ||
|
|
ae6ed99dc4 | ||
|
|
14bd58e741 | ||
|
|
6d35a7a4ba | ||
|
|
46b0d1ceac | ||
|
|
67a66d2fcd | ||
|
|
43e90b57ea | ||
|
|
c80740e590 | ||
|
|
54ccb9611e | ||
|
|
8fcb897800 | ||
|
|
699eda5d1b | ||
|
|
d7d0a83550 | ||
|
|
e3c331c911 | ||
|
|
eda4dd6aec | ||
|
|
8ad7be474d | ||
|
|
a64435f155 | ||
|
|
fa160124d2 | ||
|
|
5765cb8330 | ||
|
|
f472b227bb | ||
|
|
d2b419c42e | ||
|
|
09d4de660f | ||
|
|
728dcd8523 | ||
|
|
93cf9bf6a9 | ||
|
|
50841f5e24 | ||
|
|
fc6d92d1fc | ||
|
|
7162a029bb | ||
|
|
d797ddd668 | ||
|
|
989e8c3aa6 | ||
|
|
08b79af242 | ||
|
|
0d2f346a30 | ||
|
|
39f1d5f5fd | ||
|
|
05008bb7f8 | ||
|
|
be90d6fc45 | ||
|
|
a1bcdf9924 | ||
|
|
b0e001393c | ||
|
|
2d08941f6a | ||
|
|
d0fef1f312 | ||
|
|
68342cb0d4 | ||
|
|
2b419212a7 | ||
|
|
b2cbc7e34d | ||
|
|
61247e575b | ||
|
|
31e18266d1 | ||
|
|
df8a8de889 | ||
|
|
8a037d6b29 | ||
|
|
47b555b98c | ||
|
|
0c2dae475e | ||
|
|
dc676d04d8 | ||
|
|
15b54bff50 | ||
|
|
47bd4b4c0b | ||
|
|
3c8b36ddfe | ||
|
|
608df3fddd | ||
|
|
c092c285ee | ||
|
|
93b745e379 | ||
|
|
c18db77ade | ||
|
|
2c0b167e6b | ||
|
|
313254d0c8 | ||
|
|
6f519c97d3 | ||
|
|
17a3e16b1d | ||
|
|
8199358088 | ||
|
|
412928eeaa | ||
|
|
51e1b935bd | ||
|
|
742b51e5e2 | ||
|
|
fdb5e2eebb | ||
|
|
0192f64cd2 | ||
|
|
193298ac87 | ||
|
|
a81cb81799 | ||
|
|
ad8a7fdc9b | ||
|
|
5440afcebe | ||
|
|
715d7e664c | ||
|
|
aa182cfa68 | ||
|
|
f92dd7a872 | ||
|
|
b02b9197d0 | ||
|
|
86d02be70c | ||
|
|
cb990978ee | ||
|
|
a103202c92 | ||
|
|
9d7b133037 | ||
|
|
f727f2a1a9 | ||
|
|
03034768d9 | ||
|
|
aed3e20e08 | ||
|
|
74bac6d986 | ||
|
|
7ebecc353a | ||
|
|
f0302b0d1e | ||
|
|
0b004ad089 |
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
* @JoeMatt @lonkelle @jkcoxson
|
||||
* @JoeMatt @lonkelle
|
||||
|
||||
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
@@ -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
@@ -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
@@ -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
|
||||
22
.github/workflows/attach_build_products.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Add artifact links to pull request and related issues
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [Pull Request SideStore build]
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
artifacts-url-comments:
|
||||
name: add artifact links to pull request and related issues job
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: add artifact links to pull request and related issues step
|
||||
uses: tonyhallett/artifacts-url-comments@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
prefix: Builds for this Pull Request are available at
|
||||
suffix: Have a nice day.
|
||||
format: name
|
||||
addTo: pull
|
||||
# addTo: pullandissues
|
||||
156
.github/workflows/beta.yml
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
name: Beta SideStore build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and upload SideStore Beta
|
||||
if: startsWith(github.event.head_commit.message, '[beta]')
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-12'
|
||||
version: '14.2'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# - name: Cache rust cargo
|
||||
# id: cache-rust-cargo
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-cargo
|
||||
# with:
|
||||
# path: ~/.cargo
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
# - name: Cache rust minimuxer
|
||||
# id: cache-rust-minimuxer
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-minimuxer
|
||||
# with:
|
||||
# path: ./Dependencies/minimuxer/target
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
# - name: Cache rust em_proxy
|
||||
# id: cache-rust-em_proxy
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-em_proxy
|
||||
# with:
|
||||
# path: ./Dependencies/em_proxy/target
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install ldid
|
||||
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
|
||||
# - name: Create emotional damage
|
||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
# - name: Build minimuxer
|
||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
- name: Add beta suffix to version
|
||||
run: sed -e '/MARKETING_VERSION = .*/s/$/-beta.${{ github.run_number }}/' -i '' Build.xcconfig
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
with:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
xcodebuild -project AltStore.xcodeproj \
|
||||
-scheme AltStore \
|
||||
-sdk iphoneos \
|
||||
archive -archivePath ./archive \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
||||
ORG_IDENTIFIER=com.SideStore \
|
||||
| xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
mkdir Payload
|
||||
mkdir Payload/SideStore.app
|
||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
||||
zip -r SideStore.ipa Payload
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore.ipa
|
||||
path: SideStore.ipa
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get current date in AltStore date form
|
||||
id: date_altstore
|
||||
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload to beta release
|
||||
uses: IsaacShelton/update-existing-release@v1.3.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release: "Beta"
|
||||
tag: "beta"
|
||||
prerelease: true
|
||||
files: SideStore.ipa
|
||||
body: |
|
||||
This is an ⚠️ **EXPERIMENTAL** ⚠️ beta build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
||||
|
||||
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal, but with a lower chance of bugs than if you used nightly builds. However, since these changes are newer and less tested, they still have a good chance of bugs, so **use at your own risk**.
|
||||
|
||||
If you want to be on the bleeding edge and use the latest development builds, you can look at [SideStore Nightly](https://github.com/${{ github.repository }}/releases/tag/nightly). **Please be aware that these builds have a much higher chance of bugs than beta or stable**.
|
||||
|
||||
If you use the `SideStore (Beta)` app, it will use the latest beta build (make sure to update it in "My Apps").
|
||||
|
||||
## Build Info
|
||||
|
||||
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
Version: `${{ steps.version.outputs.version }}`
|
||||
100
.github/workflows/build.yml
vendored
@@ -1,100 +0,0 @@
|
||||
name: Build and Upload SideStore
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and upload SideStore
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-12'
|
||||
version: '14.0.0'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Cache rust cargo
|
||||
id: cache-rust-cargo
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-rust-cargo
|
||||
with:
|
||||
path: ~/.cargo
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Cache rust minimuxer
|
||||
id: cache-rust-minimuxer
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-rust-minimuxer
|
||||
with:
|
||||
path: ./Dependencies/minimuxer/target
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Cache rust em_proxy
|
||||
id: cache-rust-em_proxy
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-rust-em_proxy
|
||||
with:
|
||||
path: ./Dependencies/em_proxy/target
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install ldid
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
- name: Create emotional damage
|
||||
run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
- name: Build minimuxer
|
||||
run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
with:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
rm -rf ~/Library/Developer/Xcode/DerivedData/
|
||||
rm ./AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
|
||||
xcodebuild -project AltStore.xcodeproj -scheme AltStore -sdk iphoneos archive -archivePath ./archive CODE_SIGNING_REQUIRED=NO AD_HOC_CODE_SIGNING_ALLOWED=YES CODE_SIGNING_ALLOWED=NO DEVELOPMENT_TEAM=XYZ0123456 ORG_IDENTIFIER=com.SideStore | xcpretty && exit ${PIPESTATUS[0]}
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
mkdir Payload
|
||||
mkdir Payload/SideStore.app
|
||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
||||
zip -r SideStore.ipa Payload
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore.ipa
|
||||
path: SideStore.ipa
|
||||
155
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
name: Nightly SideStore build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and upload SideStore Nightly
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-12'
|
||||
version: '14.2'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# - name: Cache rust cargo
|
||||
# id: cache-rust-cargo
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-cargo
|
||||
# with:
|
||||
# path: ~/.cargo
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
# - name: Cache rust minimuxer
|
||||
# id: cache-rust-minimuxer
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-minimuxer
|
||||
# with:
|
||||
# path: ./Dependencies/minimuxer/target
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
# - name: Cache rust em_proxy
|
||||
# id: cache-rust-em_proxy
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-em_proxy
|
||||
# with:
|
||||
# path: ./Dependencies/em_proxy/target
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install ldid
|
||||
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
|
||||
# - name: Create emotional damage
|
||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
# - name: Build minimuxer
|
||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
- name: Add nightly suffix to version
|
||||
run: sed -e '/MARKETING_VERSION = .*/s/$/-nightly.${{ github.run_number }}/' -i '' Build.xcconfig
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
with:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
xcodebuild -project AltStore.xcodeproj \
|
||||
-scheme AltStore \
|
||||
-sdk iphoneos \
|
||||
archive -archivePath ./archive \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
||||
ORG_IDENTIFIER=com.SideStore \
|
||||
| xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
mkdir Payload
|
||||
mkdir Payload/SideStore.app
|
||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
||||
zip -r SideStore.ipa Payload
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore.ipa
|
||||
path: SideStore.ipa
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get current date in AltStore date form
|
||||
id: date_altstore
|
||||
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload to nightly release
|
||||
uses: IsaacShelton/update-existing-release@v1.3.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release: "Nightly"
|
||||
tag: "nightly"
|
||||
prerelease: true
|
||||
files: SideStore.ipa
|
||||
body: |
|
||||
This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
||||
|
||||
Nightly builds are built from the most recent commit which means you'll be able to try out new features very early. However, since these changes are much newer and less tested, they have a much higher chance of bugs, so **use at your own risk**.
|
||||
|
||||
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore Beta](https://github.com/${{ github.repository }}/releases/tag/beta).
|
||||
|
||||
If you use the `SideStore (Nightly)` app, it will use the latest nightly build (make sure to update it in "My Apps").
|
||||
|
||||
## Build Info
|
||||
|
||||
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
Version: `${{ steps.version.outputs.version }}`
|
||||
114
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
name: Pull Request SideStore build
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and upload SideStore
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-12'
|
||||
version: '14.2'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# - name: Cache rust cargo
|
||||
# id: cache-rust-cargo
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-cargo
|
||||
# with:
|
||||
# path: ~/.cargo
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
# - name: Cache rust minimuxer
|
||||
# id: cache-rust-minimuxer
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-minimuxer
|
||||
# with:
|
||||
# path: ./Dependencies/minimuxer/target
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
# - name: Cache rust em_proxy
|
||||
# id: cache-rust-em_proxy
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-em_proxy
|
||||
# with:
|
||||
# path: ./Dependencies/em_proxy/target
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install ldid
|
||||
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
|
||||
# - name: Create emotional damage
|
||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
# - 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:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
xcodebuild -project AltStore.xcodeproj \
|
||||
-scheme AltStore \
|
||||
-sdk iphoneos \
|
||||
archive -archivePath ./archive \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
||||
ORG_IDENTIFIER=com.SideStore \
|
||||
| xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
mkdir Payload
|
||||
mkdir Payload/SideStore.app
|
||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
||||
zip -r SideStore.ipa Payload
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore.ipa
|
||||
path: SideStore.ipa
|
||||
145
.github/workflows/stable.yml
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
name: Stable SideStore build
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and upload SideStore
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-12'
|
||||
version: '14.2'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# - name: Cache rust cargo
|
||||
# id: cache-rust-cargo
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-cargo
|
||||
# with:
|
||||
# path: ~/.cargo
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
# - name: Cache rust minimuxer
|
||||
# id: cache-rust-minimuxer
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-minimuxer
|
||||
# with:
|
||||
# path: ./Dependencies/minimuxer/target
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
# - name: Cache rust em_proxy
|
||||
# id: cache-rust-em_proxy
|
||||
# uses: actions/cache@v3
|
||||
# env:
|
||||
# cache-name: cache-rust-em_proxy
|
||||
# with:
|
||||
# path: ./Dependencies/em_proxy/target
|
||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
# ${{ runner.os }}-build-
|
||||
# ${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install ldid
|
||||
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
|
||||
# - name: Create emotional damage
|
||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
# - name: Build minimuxer
|
||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
with:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
xcodebuild -project AltStore.xcodeproj \
|
||||
-scheme AltStore \
|
||||
-sdk iphoneos \
|
||||
archive -archivePath ./archive \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
||||
ORG_IDENTIFIER=com.SideStore \
|
||||
| xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
mkdir Payload
|
||||
mkdir Payload/SideStore.app
|
||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
||||
zip -r SideStore.ipa Payload
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore.ipa
|
||||
path: SideStore.ipa
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get current date in AltStore date form
|
||||
id: date_altstore
|
||||
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload to new stable release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: ${{ steps.version.outputs.version }}
|
||||
tag_name: ${{ github.ref }}
|
||||
draft: true
|
||||
files: SideStore.ipa
|
||||
body: |
|
||||
## Changelog
|
||||
|
||||
- TODO
|
||||
|
||||
## Build Info
|
||||
|
||||
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
Version: `${{ steps.version.outputs.version }}`
|
||||
5
.gitmodules
vendored
@@ -21,4 +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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>ALTAppGroups</key>
|
||||
<array>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
<string>group.com.rileytestut.AltStore</string>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
</array>
|
||||
<key>ALTBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
|
||||
@@ -77,7 +77,7 @@ extension XPCConnectionHandler: NSXPCListenerDelegate
|
||||
guard
|
||||
let codeSigningInfo = signingInfo as? [String: Any],
|
||||
let bundleIdentifier = codeSigningInfo["identifier"] as? String,
|
||||
bundleIdentifier.contains("com.rileytestut.AltStore")
|
||||
bundleIdentifier.contains(Bundle.Info.appbundleIdentifier)
|
||||
else { return false }
|
||||
|
||||
let connection = XPCConnection(newConnection)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
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 = "BFD247692284B9A500981D42"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Release">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -14,13 +14,7 @@ import AppCenter
|
||||
import AppCenterAnalytics
|
||||
import AppCenterCrashes
|
||||
|
||||
#if DEBUG
|
||||
private let appCenterAppSecret = "bb08e9bb-c126-408d-bf3f-324c8473fd40"
|
||||
#elseif RELEASE
|
||||
private let appCenterAppSecret = "b6718932-294a-432b-81f2-be1e17ff85c5"
|
||||
#else
|
||||
private let appCenterAppSecret = "e873f6ca-75eb-4685-818f-801e0e375d60"
|
||||
#endif
|
||||
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
|
||||
|
||||
extension AnalyticsManager
|
||||
{
|
||||
@@ -77,7 +71,7 @@ extension AnalyticsManager
|
||||
}
|
||||
}
|
||||
|
||||
class AnalyticsManager
|
||||
final class AnalyticsManager
|
||||
{
|
||||
static let shared = AnalyticsManager()
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ extension AppContentViewController
|
||||
}
|
||||
}
|
||||
|
||||
class AppContentViewController: UITableViewController
|
||||
final class AppContentViewController: UITableViewController
|
||||
{
|
||||
var app: StoreApp!
|
||||
|
||||
@@ -80,10 +80,21 @@ class AppContentViewController: UITableViewController
|
||||
|
||||
self.subtitleLabel.text = self.app.subtitle
|
||||
self.descriptionTextView.text = self.app.localizedDescription
|
||||
self.versionDescriptionTextView.text = self.app.versionDescription
|
||||
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), self.app.version)
|
||||
self.versionDateLabel.text = Date().relativeDateString(since: self.app.versionDate, dateFormatter: self.dateFormatter)
|
||||
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: Int64(self.app.size))
|
||||
|
||||
if let version = self.app.latestVersion
|
||||
{
|
||||
self.versionDescriptionTextView.text = version.localizedDescription
|
||||
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.version)
|
||||
self.versionDateLabel.text = Date().relativeDateString(since: version.date, dateFormatter: self.dateFormatter)
|
||||
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: version.size)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.versionDescriptionTextView.text = nil
|
||||
self.versionLabel.text = nil
|
||||
self.versionDateLabel.text = nil
|
||||
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: 0)
|
||||
}
|
||||
|
||||
self.descriptionTextView.maximumNumberOfLines = 5
|
||||
self.descriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||
@@ -173,7 +184,8 @@ private extension AppContentViewController
|
||||
dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in
|
||||
let cell = cell as! PermissionCollectionViewCell
|
||||
cell.button.setImage(permission.type.icon, for: .normal)
|
||||
cell.textLabel.text = permission.type.localizedShortName
|
||||
cell.button.tintColor = .label
|
||||
cell.textLabel.text = permission.type.localizedShortName ?? permission.type.localizedName
|
||||
}
|
||||
|
||||
return dataSource
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class PermissionCollectionViewCell: UICollectionViewCell
|
||||
final class PermissionCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
@IBOutlet var button: UIButton!
|
||||
@IBOutlet var textLabel: UILabel!
|
||||
@@ -29,7 +29,7 @@ class PermissionCollectionViewCell: UICollectionViewCell
|
||||
}
|
||||
}
|
||||
|
||||
class AppContentTableViewCell: UITableViewCell
|
||||
final class AppContentTableViewCell: UITableViewCell
|
||||
{
|
||||
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ import Roxas
|
||||
|
||||
import Nuke
|
||||
|
||||
class AppViewController: UIViewController
|
||||
final class AppViewController: UIViewController
|
||||
{
|
||||
var app: StoreApp!
|
||||
|
||||
@@ -352,7 +352,7 @@ class AppViewController: UIViewController
|
||||
|
||||
extension AppViewController
|
||||
{
|
||||
class func makeAppViewController(app: StoreApp) -> AppViewController
|
||||
final class func makeAppViewController(app: StoreApp) -> AppViewController
|
||||
{
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
|
||||
@@ -384,10 +384,10 @@ private extension AppViewController
|
||||
button.progress = progress
|
||||
}
|
||||
|
||||
if Date() < self.app.versionDate
|
||||
if let versionDate = self.app.latestVersion?.date, versionDate > Date()
|
||||
{
|
||||
self.bannerView.button.countdownDate = self.app.versionDate
|
||||
self.navigationBarDownloadButton.countdownDate = self.app.versionDate
|
||||
self.bannerView.button.countdownDate = versionDate
|
||||
self.navigationBarDownloadButton.countdownDate = versionDate
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
class PermissionPopoverViewController: UIViewController
|
||||
final class PermissionPopoverViewController: UIViewController
|
||||
{
|
||||
var permission: AppPermission!
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import UIKit
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
class AppIDsViewController: UICollectionViewController
|
||||
final class AppIDsViewController: UICollectionViewController
|
||||
{
|
||||
private lazy var dataSource = self.makeDataSource()
|
||||
|
||||
|
||||
@@ -18,49 +18,49 @@ import EmotionalDamage
|
||||
|
||||
extension AppDelegate
|
||||
{
|
||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
||||
static let importAppDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.ImportAppDeepLinkNotification")
|
||||
static let addSourceDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.AddSourceDeepLinkNotification")
|
||||
|
||||
static let appBackupDidFinish = Notification.Name("com.rileytestut.AltStore.AppBackupDidFinish")
|
||||
|
||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".OpenPatreonSettingsDeepLinkNotification")
|
||||
static let importAppDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ImportAppDeepLinkNotification")
|
||||
static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification")
|
||||
|
||||
static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish")
|
||||
|
||||
static let importAppDeepLinkURLKey = "fileURL"
|
||||
static let appBackupResultKey = "result"
|
||||
static let addSourceDeepLinkURLKey = "sourceURL"
|
||||
}
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
@available(iOS 14, *)
|
||||
private var intentHandler: IntentHandler {
|
||||
get { _intentHandler as! IntentHandler }
|
||||
set { _intentHandler = newValue }
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 14, *)
|
||||
private var viewAppIntentHandler: ViewAppIntentHandler {
|
||||
get { _viewAppIntentHandler as! ViewAppIntentHandler }
|
||||
set { _viewAppIntentHandler = newValue }
|
||||
}
|
||||
|
||||
|
||||
private lazy var _intentHandler: Any = {
|
||||
guard #available(iOS 14, *) else { fatalError() }
|
||||
return IntentHandler()
|
||||
}()
|
||||
|
||||
|
||||
private lazy var _viewAppIntentHandler: Any = {
|
||||
guard #available(iOS 14, *) else { fatalError() }
|
||||
return ViewAppIntentHandler()
|
||||
}()
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
||||
{
|
||||
// Register default settings before doing anything else.
|
||||
UserDefaults.registerDefaults()
|
||||
|
||||
|
||||
DatabaseManager.shared.start { (error) in
|
||||
if let error = error
|
||||
{
|
||||
@@ -71,50 +71,62 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
print("Started DatabaseManager.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AnalyticsManager.shared.start()
|
||||
|
||||
|
||||
self.setTintColor()
|
||||
|
||||
SecureValueTransformer.register()
|
||||
|
||||
|
||||
SecureValueTransformer.register()
|
||||
|
||||
if UserDefaults.standard.firstLaunch == nil
|
||||
{
|
||||
Keychain.shared.reset()
|
||||
UserDefaults.standard.firstLaunch = Date()
|
||||
}
|
||||
|
||||
|
||||
UserDefaults.standard.preferredServerID = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.serverID) as? String
|
||||
|
||||
|
||||
#if DEBUG || BETA
|
||||
UserDefaults.standard.isDebugModeEnabled = true
|
||||
#endif
|
||||
|
||||
|
||||
self.prepareForBackgroundFetch()
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication)
|
||||
{
|
||||
{
|
||||
// Make sure to update SceneDelegate.sceneDidEnterBackground() as well.
|
||||
|
||||
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
|
||||
|
||||
let midnightOneMonthAgo = Calendar.current.startOfDay(for: oneMonthAgo)
|
||||
DatabaseManager.shared.purgeLoggedErrors(before: midnightOneMonthAgo) { result in
|
||||
switch result
|
||||
{
|
||||
case .success: break
|
||||
case .failure(let error): print("[ALTLog] Failed to purge logged errors before \(midnightOneMonthAgo).", error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication)
|
||||
{
|
||||
AppManager.shared.update()
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
}
|
||||
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
|
||||
{
|
||||
return self.open(url)
|
||||
}
|
||||
|
||||
|
||||
func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any?
|
||||
{
|
||||
guard #available(iOS 14, *) else { return nil }
|
||||
|
||||
|
||||
switch intent
|
||||
{
|
||||
case is RefreshAllIntent: return self.intentHandler
|
||||
@@ -133,7 +145,7 @@ extension AppDelegate
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>)
|
||||
{
|
||||
// Called when the user discards a scene session.
|
||||
@@ -148,36 +160,36 @@ private extension AppDelegate
|
||||
{
|
||||
self.window?.tintColor = .altPrimary
|
||||
}
|
||||
|
||||
|
||||
func open(_ url: URL) -> Bool
|
||||
{
|
||||
if url.isFileURL
|
||||
{
|
||||
guard url.pathExtension.lowercased() == "ipa" else { return false }
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: url])
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
else
|
||||
{
|
||||
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
|
||||
guard let host = components.host?.lowercased() else { return false }
|
||||
|
||||
|
||||
switch host
|
||||
{
|
||||
case "patreon":
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
|
||||
|
||||
case "appbackupresponse":
|
||||
let result: Result<Void, Error>
|
||||
|
||||
|
||||
switch url.path.lowercased()
|
||||
{
|
||||
case "/success": result = .success(())
|
||||
@@ -188,37 +200,37 @@ private extension AppDelegate
|
||||
let errorCodeString = queryItems["errorCode"], let errorCode = Int(errorCodeString),
|
||||
let errorDescription = queryItems["errorDescription"]
|
||||
else { return false }
|
||||
|
||||
|
||||
let error = NSError(domain: errorDomain, code: errorCode, userInfo: [NSLocalizedDescriptionKey: errorDescription])
|
||||
result = .failure(error)
|
||||
|
||||
|
||||
default: return false
|
||||
}
|
||||
|
||||
|
||||
NotificationCenter.default.post(name: AppDelegate.appBackupDidFinish, object: nil, userInfo: [AppDelegate.appBackupResultKey: result])
|
||||
|
||||
|
||||
return true
|
||||
|
||||
|
||||
case "install":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
guard let downloadURLString = queryItems["url"], let downloadURL = URL(string: downloadURLString) else { return false }
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: downloadURL])
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
|
||||
|
||||
case "source":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
guard let sourceURLString = queryItems["url"], let sourceURL = URL(string: sourceURLString) else { return false }
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL])
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
|
||||
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
@@ -231,47 +243,47 @@ extension AppDelegate
|
||||
{
|
||||
// "Fetch" every hour, but then refresh only those that need to be refreshed (so we don't drain the battery).
|
||||
UIApplication.shared.setMinimumBackgroundFetchInterval(1 * 60 * 60)
|
||||
|
||||
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (success, error) in
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
|
||||
{
|
||||
let tokenParts = deviceToken.map { data -> String in
|
||||
return String(format: "%02.2hhx", data)
|
||||
}
|
||||
|
||||
|
||||
let token = tokenParts.joined()
|
||||
print("Push Token:", token)
|
||||
}
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
||||
{
|
||||
self.application(application, performFetchWithCompletionHandler: completionHandler)
|
||||
}
|
||||
|
||||
|
||||
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
||||
{
|
||||
if UserDefaults.standard.isBackgroundRefreshEnabled && !UserDefaults.standard.presentedLaunchReminderNotification
|
||||
{
|
||||
let threeHours: TimeInterval = 3 * 60 * 60
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
||||
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = NSLocalizedString("App Refresh Tip", comment: "")
|
||||
content.body = NSLocalizedString("The more you open SideStore, the more chances it's given to refresh apps in the background.", comment: "")
|
||||
|
||||
|
||||
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
|
||||
|
||||
UserDefaults.standard.presentedLaunchReminderNotification = true
|
||||
}
|
||||
|
||||
|
||||
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
|
||||
if let error = taskResult.error
|
||||
{
|
||||
@@ -280,7 +292,7 @@ extension AppDelegate
|
||||
taskCompletionHandler()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if !DatabaseManager.shared.isStarted
|
||||
{
|
||||
DatabaseManager.shared.start() { (error) in
|
||||
@@ -309,7 +321,7 @@ extension AppDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func performBackgroundFetch(backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
||||
refreshAppsCompletionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
||||
{
|
||||
@@ -319,15 +331,15 @@ extension AppDelegate
|
||||
case .failure: backgroundFetchCompletionHandler(.failed)
|
||||
case .success: backgroundFetchCompletionHandler(.newData)
|
||||
}
|
||||
|
||||
|
||||
if !UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
{
|
||||
refreshAppsCompletionHandler(.success([:]))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
guard UserDefaults.standard.isBackgroundRefreshEnabled else { return }
|
||||
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
|
||||
AppManager.shared.backgroundRefresh(installedApps, completionHandler: refreshAppsCompletionHandler)
|
||||
@@ -343,49 +355,49 @@ private extension AppDelegate
|
||||
do
|
||||
{
|
||||
let (sources, context) = try result.get()
|
||||
|
||||
|
||||
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
||||
previousUpdatesFetchRequest.includesPendingChanges = false
|
||||
previousUpdatesFetchRequest.resultType = .dictionaryResultType
|
||||
previousUpdatesFetchRequest.propertiesToFetch = [#keyPath(InstalledApp.bundleIdentifier)]
|
||||
|
||||
|
||||
let previousNewsItemsFetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NSFetchRequestResult>
|
||||
previousNewsItemsFetchRequest.includesPendingChanges = false
|
||||
previousNewsItemsFetchRequest.resultType = .dictionaryResultType
|
||||
previousNewsItemsFetchRequest.propertiesToFetch = [#keyPath(NewsItem.identifier)]
|
||||
|
||||
|
||||
let previousUpdates = try context.fetch(previousUpdatesFetchRequest) as! [[String: String]]
|
||||
let previousNewsItems = try context.fetch(previousNewsItemsFetchRequest) as! [[String: String]]
|
||||
|
||||
|
||||
try context.save()
|
||||
|
||||
|
||||
let updatesFetchRequest = InstalledApp.updatesFetchRequest()
|
||||
let newsItemsFetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NewsItem>
|
||||
|
||||
|
||||
let updates = try context.fetch(updatesFetchRequest)
|
||||
let newsItems = try context.fetch(newsItemsFetchRequest)
|
||||
|
||||
|
||||
for update in updates
|
||||
{
|
||||
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
|
||||
guard let storeApp = update.storeApp else { continue }
|
||||
|
||||
guard let storeApp = update.storeApp, let version = storeApp.version else { continue }
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = NSLocalizedString("New Update Available", comment: "")
|
||||
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, storeApp.version)
|
||||
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, version)
|
||||
content.sound = .default
|
||||
|
||||
|
||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
}
|
||||
|
||||
|
||||
for newsItem in newsItems
|
||||
{
|
||||
guard !previousNewsItems.contains(where: { $0[#keyPath(NewsItem.identifier)] == newsItem.identifier }) else { continue }
|
||||
guard !newsItem.isSilent else { continue }
|
||||
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
|
||||
if let app = newsItem.storeApp
|
||||
{
|
||||
content.title = String(format: NSLocalizedString("%@ News", comment: ""), app.name)
|
||||
@@ -394,10 +406,10 @@ private extension AppDelegate
|
||||
{
|
||||
content.title = NSLocalizedString("SideStore News", comment: "")
|
||||
}
|
||||
|
||||
|
||||
content.body = newsItem.title
|
||||
content.sound = .default
|
||||
|
||||
|
||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
}
|
||||
@@ -405,7 +417,7 @@ private extension AppDelegate
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
||||
}
|
||||
|
||||
|
||||
completionHandler(.success(sources))
|
||||
}
|
||||
catch
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
import AltSign
|
||||
|
||||
class AuthenticationViewController: UIViewController
|
||||
final class AuthenticationViewController: UIViewController
|
||||
{
|
||||
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
|
||||
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
|
||||
@@ -31,7 +31,7 @@ class AuthenticationViewController: UIViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.signInButton.activityIndicatorView.style = .white
|
||||
self.signInButton.activityIndicatorView.style = .medium
|
||||
|
||||
for view in [self.appleIDBackgroundView!, self.passwordBackgroundView!, self.signInButton!]
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class InstructionsViewController: UIViewController
|
||||
final class InstructionsViewController: UIViewController
|
||||
{
|
||||
var completionHandler: (() -> Void)?
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
class RefreshAltStoreViewController: UIViewController
|
||||
final class RefreshAltStoreViewController: UIViewController
|
||||
{
|
||||
var context: AuthenticatedOperationContext!
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import IntentsUI
|
||||
|
||||
import AltSign
|
||||
|
||||
class SelectTeamViewController: UITableViewController
|
||||
final class SelectTeamViewController: UITableViewController
|
||||
{
|
||||
public var teams: [ALTTeam]?
|
||||
public var completionHandler: ((Result<ALTTeam, Swift.Error>) -> Void)?
|
||||
|
||||
@@ -1095,13 +1095,13 @@ World</string>
|
||||
<image name="News" width="19" height="20"/>
|
||||
<image name="Settings" width="20" height="20"/>
|
||||
<namedColor name="Background">
|
||||
<color red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="BlurTint">
|
||||
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="1" green="1" blue="1" alpha="0.3" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="Primary">
|
||||
<color red="0.50196078431372548" green="0.2627450980392157" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
|
||||
@@ -12,7 +12,7 @@ import Roxas
|
||||
|
||||
import Nuke
|
||||
|
||||
@objc class BrowseCollectionViewCell: UICollectionViewCell
|
||||
@objc final class BrowseCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
var imageURLs: [URL] = [] {
|
||||
didSet {
|
||||
|
||||
@@ -94,7 +94,7 @@ private extension BrowseViewController
|
||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||
|
||||
cell.bannerView.button.addTarget(self, action: #selector(BrowseViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
||||
cell.bannerView.button.activityIndicatorView.style = .white
|
||||
cell.bannerView.button.activityIndicatorView.style = .medium
|
||||
|
||||
// Explicitly set to false to ensure we're starting from a non-activity indicating state.
|
||||
// Otherwise, cell reuse can mess up some cached values.
|
||||
@@ -113,7 +113,7 @@ private extension BrowseViewController
|
||||
let progress = AppManager.shared.installationProgress(for: app)
|
||||
cell.bannerView.button.progress = progress
|
||||
|
||||
if Date() < app.versionDate
|
||||
if let versionDate = app.latestVersion?.date, versionDate > Date()
|
||||
{
|
||||
cell.bannerView.button.countdownDate = app.versionDate
|
||||
}
|
||||
@@ -167,14 +167,7 @@ private extension BrowseViewController
|
||||
|
||||
func updateDataSource()
|
||||
{
|
||||
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||
{
|
||||
self.dataSource.predicate = nil
|
||||
}
|
||||
else
|
||||
{
|
||||
self.dataSource.predicate = NSPredicate(format: "%K == NO", #keyPath(StoreApp.isBeta))
|
||||
}
|
||||
}
|
||||
|
||||
func fetchSource()
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class AppIconImageView: UIImageView
|
||||
final class AppIconImageView: UIImageView
|
||||
{
|
||||
override func awakeFromNib()
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import AVFoundation
|
||||
|
||||
class BackgroundTaskManager
|
||||
final class BackgroundTaskManager
|
||||
{
|
||||
static let shared = BackgroundTaskManager()
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class BannerCollectionViewCell: UICollectionViewCell
|
||||
final class BannerCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
private(set) var errorBadge: UIView?
|
||||
@IBOutlet private(set) var bannerView: AppBannerView!
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class Button: UIButton
|
||||
final class Button: UIButton
|
||||
{
|
||||
override var intrinsicContentSize: CGSize {
|
||||
var size = super.intrinsicContentSize
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class CollapsingTextView: UITextView
|
||||
final class CollapsingTextView: UITextView
|
||||
{
|
||||
var isCollapsed = true {
|
||||
didSet {
|
||||
@@ -71,8 +71,10 @@ class CollapsingTextView: UITextView
|
||||
{
|
||||
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
||||
|
||||
let maximumCollapsedHeight = font.lineHeight * CGFloat(self.maximumNumberOfLines)
|
||||
if self.intrinsicContentSize.height > maximumCollapsedHeight
|
||||
let boundingSize = self.attributedText.boundingRect(with: CGSize(width: self.textContainer.size.width, height: .infinity), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
|
||||
let maximumCollapsedHeight = font.lineHeight * Double(self.maximumNumberOfLines)
|
||||
|
||||
if boundingSize.height.rounded() > maximumCollapsedHeight.rounded()
|
||||
{
|
||||
var exclusionFrame = moreButtonFrame
|
||||
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class ForwardingNavigationController: UINavigationController
|
||||
final class ForwardingNavigationController: UINavigationController
|
||||
{
|
||||
override var childForStatusBarStyle: UIViewController? {
|
||||
return self.topViewController
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
import Roxas
|
||||
|
||||
class NavigationBar: UINavigationBar
|
||||
final class NavigationBar: UINavigationBar
|
||||
{
|
||||
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class PillButton: UIButton
|
||||
final class PillButton: UIButton
|
||||
{
|
||||
override var accessibilityValue: String? {
|
||||
get {
|
||||
@@ -88,7 +88,7 @@ class PillButton: UIButton
|
||||
self.layer.masksToBounds = true
|
||||
self.accessibilityTraits.formUnion([.updatesFrequently, .button])
|
||||
|
||||
self.activityIndicatorView.style = .white
|
||||
self.activityIndicatorView.style = .medium
|
||||
self.activityIndicatorView.isUserInteractionEnabled = false
|
||||
|
||||
self.progressView.progress = 0
|
||||
|
||||
@@ -16,7 +16,7 @@ extension TimeInterval
|
||||
static let longToastViewDuration = 8.0
|
||||
}
|
||||
|
||||
class ToastView: RSTToastView
|
||||
final class ToastView: RSTToastView
|
||||
{
|
||||
var preferredDuration: TimeInterval
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
let customLog = OSLog(subsystem: "org.sidestore.sidestore",
|
||||
public let customLog = OSLog(subsystem: "org.sidestore.sidestore",
|
||||
category: "ios")
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ public extension OSLog {
|
||||
/// - Parameters:
|
||||
/// - message: String or format string
|
||||
/// - args: optional args for format string
|
||||
@inlinable
|
||||
static func error(_ message: StaticString, _ args: CVarArg...) {
|
||||
os_log(message, log: customLog, type: .error, args)
|
||||
}
|
||||
@@ -26,6 +27,7 @@ public extension OSLog {
|
||||
/// - Parameters:
|
||||
/// - message: String or format string
|
||||
/// - args: optional args for format string
|
||||
@inlinable
|
||||
static func info(_ message: StaticString, _ args: CVarArg...) {
|
||||
os_log(message, log: customLog, type: .info, args)
|
||||
}
|
||||
@@ -34,6 +36,7 @@ public extension OSLog {
|
||||
/// - Parameters:
|
||||
/// - message: String or format string
|
||||
/// - args: optional args for format string
|
||||
@inlinable
|
||||
static func debug(_ message: StaticString, _ args: CVarArg...) {
|
||||
os_log(message, log: customLog, type: .debug, args)
|
||||
}
|
||||
@@ -45,6 +48,7 @@ public extension OSLog {
|
||||
/// - Parameters:
|
||||
/// - message: String or format string
|
||||
/// - args: optional args for format string
|
||||
@inlinable
|
||||
public func ELOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
||||
OSLog.error(message, args)
|
||||
}
|
||||
@@ -53,6 +57,7 @@ public func ELOG(_ message: StaticString, file: StaticString = #file, function:
|
||||
/// - Parameters:
|
||||
/// - message: String or format string
|
||||
/// - args: optional args for format string
|
||||
@inlinable
|
||||
public func ILOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
||||
OSLog.info(message, args)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>ALTAppGroups</key>
|
||||
<array>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
<string>group.com.rileytestut.AltStore</string>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
</array>
|
||||
<key>ALTDeviceID</key>
|
||||
<string>00008101-000129D63698001E</string>
|
||||
@@ -44,6 +44,8 @@
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -56,6 +58,7 @@
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>altstore</string>
|
||||
<string>sidestore</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
@@ -66,6 +69,7 @@
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>altstore-com.rileytestut.AltStore</string>
|
||||
<string>sidestore-com.SideStore.SideStore</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -200,5 +204,7 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
import AltStoreCore
|
||||
|
||||
@available(iOS 14, *)
|
||||
class IntentHandler: NSObject, RefreshAllIntentHandling
|
||||
final class IntentHandler: NSObject, RefreshAllIntentHandling
|
||||
{
|
||||
private let queue = DispatchQueue(label: "io.altstore.IntentHandler")
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import minimuxer
|
||||
import AltStoreCore
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||
{
|
||||
private var didFinishLaunching = false
|
||||
|
||||
@@ -47,6 +47,7 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(true)
|
||||
#if !targetEnvironment(simulator)
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
guard let pf = fetchPairingFile() else {
|
||||
@@ -54,6 +55,7 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||
return
|
||||
}
|
||||
start_minimuxer_threads(pf)
|
||||
#endif
|
||||
}
|
||||
|
||||
func fetchPairingFile() -> String? {
|
||||
@@ -83,8 +85,10 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||
// Try to load it from a file picker
|
||||
var types = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil)
|
||||
types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: nil))
|
||||
types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data))
|
||||
types.append(.xml)
|
||||
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
|
||||
documentPickerController.shouldShowFileExtensions = true
|
||||
documentPickerController.delegate = self
|
||||
self.present(documentPickerController, animated: true, completion: nil)
|
||||
})
|
||||
@@ -140,12 +144,22 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||
}
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||
displayError("Choosing a pairing file was cancelled")
|
||||
displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
|
||||
}
|
||||
|
||||
func start_minimuxer_threads(_ pairing_file: String) {
|
||||
set_usbmuxd_socket()
|
||||
#if false // Retries
|
||||
var res = start_minimuxer(pairing_file: pairing_file)
|
||||
var attempts = 10
|
||||
while (attempts != 0 && res != 0) {
|
||||
print("start_minimuxer `res` != 0, retry #\(attempts)")
|
||||
res = start_minimuxer(pairing_file: pairing_file)
|
||||
attempts -= 1
|
||||
}
|
||||
#else
|
||||
let res = start_minimuxer(pairing_file: pairing_file)
|
||||
#endif
|
||||
if res != 0 {
|
||||
displayError("minimuxer failed to start. Incorrect arguments were passed.")
|
||||
}
|
||||
@@ -165,7 +179,19 @@ extension LaunchViewController
|
||||
{
|
||||
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "")
|
||||
|
||||
let alertController = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert)
|
||||
let errorDescription: String
|
||||
|
||||
if #available(iOS 14.5, *)
|
||||
{
|
||||
let errorMessages = [error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }
|
||||
errorDescription = errorMessages.joined(separator: "\n\n")
|
||||
}
|
||||
else
|
||||
{
|
||||
errorDescription = error.debugDescription
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: errorDescription, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { (action) in
|
||||
self.handleLaunchConditions()
|
||||
}))
|
||||
|
||||
@@ -28,7 +28,7 @@ extension AppManager
|
||||
}
|
||||
|
||||
@available(iOS 13, *)
|
||||
class AppManagerPublisher: ObservableObject
|
||||
final class AppManagerPublisher: ObservableObject
|
||||
{
|
||||
@Published
|
||||
fileprivate(set) var installationProgress = [String: Progress]()
|
||||
@@ -42,7 +42,7 @@ private func ==(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Boo
|
||||
return (lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion)
|
||||
}
|
||||
|
||||
class AppManager
|
||||
final class AppManager
|
||||
{
|
||||
static let shared = AppManager()
|
||||
|
||||
@@ -664,7 +664,7 @@ extension AppManager
|
||||
@available(iOS 14, *)
|
||||
func enableJIT(for installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
class Context: OperationContext, EnableJITContext
|
||||
final class Context: OperationContext, EnableJITContext
|
||||
{
|
||||
var installedApp: InstalledApp?
|
||||
}
|
||||
@@ -684,7 +684,7 @@ extension AppManager
|
||||
@available(iOS 14.0, *)
|
||||
func patch(resignedApp: ALTApplication, presentingViewController: UIViewController, context authContext: AuthenticatedOperationContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> PatchAppOperation
|
||||
{
|
||||
class Context: InstallAppOperationContext, PatchAppContext
|
||||
final class Context: InstallAppOperationContext, PatchAppContext
|
||||
{
|
||||
}
|
||||
|
||||
@@ -758,7 +758,7 @@ extension AppManager
|
||||
extension AppManager
|
||||
{
|
||||
@discardableResult
|
||||
func backgroundRefresh(_ installedApps: [InstalledApp], presentsNotifications: Bool = true, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void) -> BackgroundRefreshAppsOperation
|
||||
func backgroundRefresh(_ installedApps: [InstalledApp], presentsNotifications: Bool = false, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void) -> BackgroundRefreshAppsOperation
|
||||
{
|
||||
let backgroundRefreshAppsOperation = BackgroundRefreshAppsOperation(installedApps: installedApps)
|
||||
backgroundRefreshAppsOperation.resultHandler = completionHandler
|
||||
@@ -1121,7 +1121,7 @@ private extension AppManager
|
||||
presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -1223,7 +1223,7 @@ private extension AppManager
|
||||
let progress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
|
||||
let context = AppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
|
||||
context.app = ALTApplication(fileURL: app.url)
|
||||
context.app = ALTApplication(fileURL: app.fileURL)
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
@@ -1662,13 +1662,8 @@ private extension AppManager
|
||||
}
|
||||
|
||||
if #available(iOS 14, *)
|
||||
{
|
||||
WidgetCenter.shared.getCurrentConfigurations { (result) in
|
||||
guard case .success(let widgets) = result else { return }
|
||||
|
||||
guard let widget = widgets.first(where: { $0.configuration is ViewAppIntent }) else { return }
|
||||
WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
|
||||
}
|
||||
{
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
}
|
||||
|
||||
do { try installedApp.managedObjectContext?.save() }
|
||||
@@ -1677,6 +1672,8 @@ private extension AppManager
|
||||
catch
|
||||
{
|
||||
group.set(.failure(error), forAppWithBundleIdentifier: operation.bundleIdentifier)
|
||||
|
||||
self.log(error, for: operation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1701,6 +1698,43 @@ private extension AppManager
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
}
|
||||
|
||||
func log(_ error: Error, for operation: AppOperation)
|
||||
{
|
||||
// Sanitize NSError on same thread before performing background task.
|
||||
let sanitizedError = (error as NSError).sanitizedForCoreData()
|
||||
|
||||
let loggedErrorOperation: LoggedError.Operation = {
|
||||
switch operation
|
||||
{
|
||||
case .install: return .install
|
||||
case .update: return .update
|
||||
case .refresh: return .refresh
|
||||
case .activate: return .activate
|
||||
case .deactivate: return .deactivate
|
||||
case .backup: return .backup
|
||||
case .restore: return .restore
|
||||
}
|
||||
}()
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
var app = operation.app
|
||||
if let managedApp = app as? NSManagedObject, let tempApp = context.object(with: managedApp.objectID) as? AppProtocol
|
||||
{
|
||||
app = tempApp
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
_ = LoggedError(error: sanitizedError, app: app, operation: loggedErrorOperation, context: context)
|
||||
try context.save()
|
||||
}
|
||||
catch let saveError
|
||||
{
|
||||
print("[ALTLog] Failed to log error \(sanitizedError.domain) code \(sanitizedError.code) for \(app.bundleIdentifier):", saveError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func run(_ operations: [Foundation.Operation], context: OperationContext?, requiresSerialQueue: Bool = false)
|
||||
{
|
||||
// Find "Install AltStore" operation if it already exists in `context`
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class InstalledAppsCollectionHeaderView: UICollectionReusableView
|
||||
final class InstalledAppsCollectionHeaderView: UICollectionReusableView
|
||||
{
|
||||
let textLabel: UILabel
|
||||
let button: UIButton
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import Roxas
|
||||
|
||||
class InstalledAppCollectionViewCell: UICollectionViewCell
|
||||
final class InstalledAppCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
private(set) var deactivateBadge: UIView?
|
||||
|
||||
@@ -55,13 +55,13 @@ class InstalledAppCollectionViewCell: UICollectionViewCell
|
||||
}
|
||||
}
|
||||
|
||||
class InstalledAppsCollectionFooterView: UICollectionReusableView
|
||||
final class InstalledAppsCollectionFooterView: UICollectionReusableView
|
||||
{
|
||||
@IBOutlet var textLabel: UILabel!
|
||||
@IBOutlet var button: UIButton!
|
||||
}
|
||||
|
||||
class NoUpdatesCollectionViewCell: UICollectionViewCell
|
||||
final class NoUpdatesCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
@IBOutlet var blurView: UIVisualEffectView!
|
||||
|
||||
@@ -73,7 +73,7 @@ class NoUpdatesCollectionViewCell: UICollectionViewCell
|
||||
}
|
||||
}
|
||||
|
||||
class UpdatesCollectionHeaderView: UICollectionReusableView
|
||||
final class UpdatesCollectionHeaderView: UICollectionReusableView
|
||||
{
|
||||
let button = PillButton(type: .system)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ extension MyAppsViewController
|
||||
}
|
||||
}
|
||||
|
||||
class MyAppsViewController: UICollectionViewController
|
||||
final class MyAppsViewController: UICollectionViewController
|
||||
{
|
||||
private let coordinator = NSFileCoordinator()
|
||||
private let operationQueue = OperationQueue()
|
||||
@@ -186,7 +186,7 @@ private extension MyAppsViewController
|
||||
func makeUpdatesDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
|
||||
{
|
||||
let fetchRequest = InstalledApp.updatesFetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.versionDate, ascending: true),
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestVersion?.date, ascending: true),
|
||||
NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)]
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
|
||||
@@ -195,7 +195,7 @@ private extension MyAppsViewController
|
||||
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
|
||||
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
|
||||
guard let self = self else { return }
|
||||
guard let app = installedApp.storeApp else { return }
|
||||
guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return }
|
||||
|
||||
let cell = cell as! UpdateCollectionViewCell
|
||||
cell.layoutMargins.left = self.view.layoutMargins.left
|
||||
@@ -209,7 +209,7 @@ private extension MyAppsViewController
|
||||
|
||||
cell.bannerView.configure(for: app)
|
||||
|
||||
let versionDate = Date().relativeDateString(since: app.versionDate, dateFormatter: self.dateFormatter)
|
||||
let versionDate = Date().relativeDateString(since: latestVersion.date, dateFormatter: self.dateFormatter)
|
||||
cell.bannerView.subtitleLabel.text = versionDate
|
||||
|
||||
let appName: String
|
||||
@@ -223,7 +223,7 @@ private extension MyAppsViewController
|
||||
appName = app.name
|
||||
}
|
||||
|
||||
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, app.version, versionDate)
|
||||
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestVersion.version, versionDate)
|
||||
|
||||
cell.bannerView.button.isIndicatingActivity = false
|
||||
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
|
||||
@@ -461,17 +461,10 @@ private extension MyAppsViewController
|
||||
|
||||
func updateDataSource()
|
||||
{
|
||||
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||
{
|
||||
|
||||
self.dataSource.predicate = nil
|
||||
}
|
||||
else
|
||||
{
|
||||
self.dataSource.predicate = NSPredicate(format: "%K == nil OR %K == NO OR %K == %@",
|
||||
#keyPath(InstalledApp.storeApp),
|
||||
#keyPath(InstalledApp.storeApp.isBeta),
|
||||
#keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,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()
|
||||
|
||||
@@ -17,7 +17,7 @@ extension UpdateCollectionViewCell
|
||||
}
|
||||
}
|
||||
|
||||
@objc class UpdateCollectionViewCell: UICollectionViewCell
|
||||
@objc final class UpdateCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
var mode: Mode = .expanded {
|
||||
didSet {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class NewsCollectionViewCell: UICollectionViewCell
|
||||
final class NewsCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
@IBOutlet var titleLabel: UILabel!
|
||||
@IBOutlet var captionLabel: UILabel!
|
||||
|
||||
@@ -14,7 +14,7 @@ import Roxas
|
||||
|
||||
import Nuke
|
||||
|
||||
private class AppBannerFooterView: UICollectionReusableView
|
||||
private final class AppBannerFooterView: UICollectionReusableView
|
||||
{
|
||||
let bannerView = AppBannerView(frame: .zero)
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)
|
||||
@@ -41,7 +41,7 @@ private class AppBannerFooterView: UICollectionReusableView
|
||||
}
|
||||
}
|
||||
|
||||
class NewsViewController: UICollectionViewController
|
||||
final class NewsViewController: UICollectionViewController
|
||||
{
|
||||
private lazy var dataSource = self.makeDataSource()
|
||||
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
|
||||
@@ -391,7 +391,7 @@ extension NewsViewController
|
||||
let progress = AppManager.shared.installationProgress(for: storeApp)
|
||||
footerView.bannerView.button.progress = progress
|
||||
|
||||
if Date() < storeApp.versionDate
|
||||
if let versionDate = storeApp.latestVersion?.date, versionDate > Date()
|
||||
{
|
||||
footerView.bannerView.button.countdownDate = storeApp.versionDate
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ enum AuthenticationError: LocalizedError
|
||||
}
|
||||
|
||||
@objc(AuthenticationOperation)
|
||||
class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)>
|
||||
final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)>
|
||||
{
|
||||
let context: AuthenticatedOperationContext
|
||||
|
||||
|
||||
@@ -51,12 +51,12 @@ private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, Uns
|
||||
}
|
||||
|
||||
@objc(BackgroundRefreshAppsOperation)
|
||||
class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledApp, Error>]>
|
||||
final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledApp, Error>]>
|
||||
{
|
||||
let installedApps: [InstalledApp]
|
||||
private let managedObjectContext: NSManagedObjectContext
|
||||
|
||||
var presentsFinishedNotification: Bool = true
|
||||
var presentsFinishedNotification: Bool = false
|
||||
|
||||
private let refreshIdentifier: String = UUID().uuidString
|
||||
private var runningApplications: Set<String> = []
|
||||
@@ -189,12 +189,12 @@ private extension BackgroundRefreshAppsOperation
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
var shouldPresentAlert = true
|
||||
var shouldPresentAlert = false
|
||||
|
||||
do
|
||||
{
|
||||
let results = try result.get()
|
||||
shouldPresentAlert = !results.isEmpty
|
||||
shouldPresentAlert = false
|
||||
|
||||
for (_, result) in results
|
||||
{
|
||||
@@ -216,7 +216,7 @@ private extension BackgroundRefreshAppsOperation
|
||||
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||
content.body = error.localizedDescription
|
||||
|
||||
shouldPresentAlert = true
|
||||
shouldPresentAlert = false
|
||||
}
|
||||
|
||||
if shouldPresentAlert
|
||||
|
||||
@@ -14,7 +14,7 @@ import Roxas
|
||||
import minimuxer
|
||||
|
||||
@objc(DeactivateAppOperation)
|
||||
class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
let app: InstalledApp
|
||||
let context: OperationContext
|
||||
|
||||
@@ -30,17 +30,17 @@ private extension DownloadAppOperation
|
||||
}
|
||||
|
||||
@objc(DownloadAppOperation)
|
||||
class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
{
|
||||
let app: AppProtocol
|
||||
let context: AppOperationContext
|
||||
|
||||
private let bundleIdentifier: String
|
||||
private let sourceURL: URL
|
||||
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)
|
||||
{
|
||||
@@ -69,7 +69,9 @@ class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
|
||||
print("Downloading App:", self.bundleIdentifier)
|
||||
|
||||
self.downloadApp(from: self.sourceURL) { result in
|
||||
guard let sourceURL = self.sourceURL else { return self.finish(.failure(OperationError.appNotFound)) }
|
||||
|
||||
self.downloadApp(from: sourceURL) { result in
|
||||
do
|
||||
{
|
||||
let application = try result.get()
|
||||
@@ -165,7 +167,7 @@ private extension DownloadAppOperation
|
||||
}
|
||||
}
|
||||
|
||||
if self.sourceURL.isFileURL
|
||||
if sourceURL.isFileURL
|
||||
{
|
||||
finishOperation(.success(sourceURL))
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ protocol EnableJITContext
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
{
|
||||
let context: Context
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import AltSign
|
||||
import Roxas
|
||||
|
||||
@objc(FetchAnisetteDataOperation)
|
||||
class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
||||
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
||||
{
|
||||
let context: OperationContext
|
||||
|
||||
@@ -34,29 +34,31 @@ 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) {
|
||||
self.finish(.success(anisette))
|
||||
}
|
||||
}
|
||||
} catch let error as NSError {
|
||||
print("Failed to load: \(error.localizedDescription)")
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import AltSign
|
||||
import Roxas
|
||||
|
||||
@objc(FetchAppIDsOperation)
|
||||
class FetchAppIDsOperation: ResultOperation<([AppID], NSManagedObjectContext)>
|
||||
final class FetchAppIDsOperation: ResultOperation<([AppID], NSManagedObjectContext)>
|
||||
{
|
||||
let context: AuthenticatedOperationContext
|
||||
let managedObjectContext: NSManagedObjectContext
|
||||
|
||||
@@ -13,7 +13,7 @@ import AltSign
|
||||
import Roxas
|
||||
|
||||
@objc(FetchProvisioningProfilesOperation)
|
||||
class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]>
|
||||
final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]>
|
||||
{
|
||||
let context: AppOperationContext
|
||||
|
||||
@@ -131,19 +131,19 @@ extension FetchProvisioningProfilesOperation
|
||||
// or if installedApp.team is nil but resignedBundleIdentifier contains the team's identifier.
|
||||
let teamsMatch = installedApp.team?.identifier == team.identifier || (installedApp.team == nil && installedApp.resignedBundleIdentifier.contains(team.identifier))
|
||||
|
||||
#if DEBUG
|
||||
|
||||
if app.isAltStoreApp
|
||||
{
|
||||
// Use legacy bundle ID format for AltStore.
|
||||
preferredBundleID = teamsMatch ? installedApp.resignedBundleIdentifier : nil
|
||||
}
|
||||
else
|
||||
{
|
||||
preferredBundleID = teamsMatch ? installedApp.resignedBundleIdentifier : nil
|
||||
}
|
||||
|
||||
#else
|
||||
// #if DEBUG
|
||||
//
|
||||
// if app.isAltStoreApp
|
||||
// {
|
||||
// // Use legacy bundle ID format for AltStore.
|
||||
// preferredBundleID = teamsMatch ? installedApp.resignedBundleIdentifier : nil
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// preferredBundleID = teamsMatch ? installedApp.resignedBundleIdentifier : nil
|
||||
// }
|
||||
//
|
||||
// #else
|
||||
|
||||
if teamsMatch
|
||||
{
|
||||
@@ -157,7 +157,7 @@ extension FetchProvisioningProfilesOperation
|
||||
preferredBundleID = nil
|
||||
}
|
||||
|
||||
#endif
|
||||
// #endif
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
class FetchSourceOperation: ResultOperation<Source>
|
||||
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 @@ 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +32,13 @@ extension FetchTrustedSourcesOperation
|
||||
}
|
||||
}
|
||||
|
||||
class FetchTrustedSourcesOperation: ResultOperation<[FetchTrustedSourcesOperation.TrustedSource]>
|
||||
final class FetchTrustedSourcesOperation: ResultOperation<[FetchTrustedSourcesOperation.TrustedSource]>
|
||||
{
|
||||
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
|
||||
|
||||
@@ -13,7 +13,7 @@ import AltSign
|
||||
import Roxas
|
||||
|
||||
@objc(InstallAppOperation)
|
||||
class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
let context: InstallAppOperationContext
|
||||
|
||||
@@ -86,6 +86,11 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
let resignedBundleID = appExtension.bundleIdentifier
|
||||
let originalBundleID = resignedBundleID.replacingOccurrences(of: resignedParentBundleID, with: parentBundleID)
|
||||
|
||||
print("`parentBundleID`: \(parentBundleID)")
|
||||
print("`resignedParentBundleID`: \(resignedParentBundleID)")
|
||||
print("`resignedBundleID`: \(resignedBundleID)")
|
||||
print("`originalBundleID`: \(originalBundleID)")
|
||||
|
||||
let installedExtension: InstalledExtension
|
||||
|
||||
if let appExtension = installedApp.appExtensions.first(where: { $0.bundleIdentifier == originalBundleID })
|
||||
|
||||
@@ -38,7 +38,7 @@ class OperationContext
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticatedOperationContext: OperationContext
|
||||
final class AuthenticatedOperationContext: OperationContext
|
||||
{
|
||||
var session: ALTAppleAPISession?
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import AltSign
|
||||
|
||||
enum OperationError: LocalizedError
|
||||
{
|
||||
static let domain = OperationError.unknown._domain
|
||||
|
||||
case unknown
|
||||
case unknownResult
|
||||
case cancelled
|
||||
|
||||
@@ -52,7 +52,7 @@ private struct OTAUpdate
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
class PatchAppOperation: ResultOperation<Void>
|
||||
final class PatchAppOperation: ResultOperation<Void>
|
||||
{
|
||||
let context: PatchAppContext
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ extension PatchViewController
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class PatchViewController: UIViewController
|
||||
final class PatchViewController: UIViewController
|
||||
{
|
||||
var patchApp: AnyApp?
|
||||
var installedApp: InstalledApp?
|
||||
|
||||
@@ -14,7 +14,7 @@ import Roxas
|
||||
import minimuxer
|
||||
|
||||
@objc(RefreshAppOperation)
|
||||
class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
let context: AppOperationContext
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import CoreData
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
|
||||
class RefreshGroup: NSObject
|
||||
final class RefreshGroup: NSObject
|
||||
{
|
||||
let context: AuthenticatedOperationContext
|
||||
let progress = Progress.discreteProgress(totalUnitCount: 0)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
@objc(RemoveAppBackupOperation)
|
||||
class RemoveAppBackupOperation: ResultOperation<Void>
|
||||
final class RemoveAppBackupOperation: ResultOperation<Void>
|
||||
{
|
||||
let context: InstallAppOperationContext
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import AltStoreCore
|
||||
import minimuxer
|
||||
|
||||
@objc(RemoveAppOperation)
|
||||
class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||
final class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
let context: InstallAppOperationContext
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import AltStoreCore
|
||||
import AltSign
|
||||
|
||||
@objc(ResignAppOperation)
|
||||
class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
{
|
||||
let context: InstallAppOperationContext
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import Network
|
||||
import AltStoreCore
|
||||
|
||||
@objc(SendAppOperation)
|
||||
class SendAppOperation: ResultOperation<()>
|
||||
final class SendAppOperation: ResultOperation<()>
|
||||
{
|
||||
let context: InstallAppOperationContext
|
||||
|
||||
@@ -39,9 +39,10 @@ class SendAppOperation: ResultOperation<()>
|
||||
guard let resignedApp = self.context.resignedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
// self.context.resignedApp.fileURL points to the app bundle, but we want the .ipa.
|
||||
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.url)
|
||||
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL)
|
||||
let fileURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
|
||||
print("AFC App `fileURL`: \(fileURL.absoluteString)")
|
||||
|
||||
let ns_bundle = NSString(string: app.bundleIdentifier)
|
||||
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
|
||||
@@ -53,6 +54,7 @@ class SendAppOperation: ResultOperation<()>
|
||||
}
|
||||
let res = minimuxer_yeet_app_afc(ns_bundle_ptr, pls, UInt(data.length))
|
||||
if res == 0 {
|
||||
print("minimuxer_yeet_app_afc `res` == \(res)")
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} else {
|
||||
|
||||
@@ -30,7 +30,7 @@ extension UpdatePatronsOperation
|
||||
}
|
||||
}
|
||||
|
||||
class UpdatePatronsOperation: ResultOperation<Void>
|
||||
final class UpdatePatronsOperation: ResultOperation<Void>
|
||||
{
|
||||
let context: NSManagedObjectContext
|
||||
|
||||
@@ -43,7 +43,7 @@ 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
|
||||
|
||||
@@ -55,7 +55,7 @@ enum VerificationError: ALTLocalizedError
|
||||
}
|
||||
|
||||
@objc(VerifyAppOperation)
|
||||
class VerifyAppOperation: ResultOperation<Void>
|
||||
final class VerifyAppOperation: ResultOperation<Void>
|
||||
{
|
||||
let context: AppOperationContext
|
||||
var verificationHandler: ((VerificationError) -> Bool)?
|
||||
|
||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 846 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 997 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.8 KiB |
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "67",
|
||||
"red" : "128"
|
||||
"blue" : "175",
|
||||
"green" : "4",
|
||||
"red" : "115"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -23,9 +23,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "103",
|
||||
"green" : "0",
|
||||
"red" : "60"
|
||||
"blue" : "150",
|
||||
"green" : "3",
|
||||
"red" : "99"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "103",
|
||||
"green" : "0",
|
||||
"red" : "60"
|
||||
"blue" : "150",
|
||||
"green" : "3",
|
||||
"red" : "99"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -23,9 +23,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "67",
|
||||
"red" : "128"
|
||||
"blue" : "175",
|
||||
"green" : "4",
|
||||
"red" : "115"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "sound@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "sound@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "fetch@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "fetch@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||