@wangyaoshen/remux 0.3.8-dev.bab6c95 → 0.3.9-dev.390cb29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +191 -17
- package/apps/ios/Remux.xcodeproj/project.pbxproj +21 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/Contents.json +23 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_120x120.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_152x152.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_167x167.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_180x180.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_20x20.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_29x29.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_40x40.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_58x58.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_60x60.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_76x76.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_80x80.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_87x87.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/Contents.json +6 -0
- package/apps/ios/Sources/Remux/RootView.swift +2 -2
- package/apps/ios/Sources/Remux/Views/Settings/MeView.swift +11 -4
- package/apps/macos/Sources/Remux/Views/ConnectionView.swift +4 -8
- package/package.json +1 -1
- package/packages/RemuxKit/Sources/RemuxKit/Models/ProtocolModels.swift +64 -0
- package/packages/RemuxKit/Sources/RemuxKit/Networking/MessageRouter.swift +88 -9
- package/packages/RemuxKit/Sources/RemuxKit/Networking/RemuxConnection.swift +47 -8
- package/packages/RemuxKit/Sources/RemuxKit/State/RemuxState.swift +81 -8
- package/packages/RemuxKit/Sources/RemuxKit/Storage/KeychainStore.swift +20 -1
- package/packages/RemuxKit/Tests/RemuxKitTests/ConnectionIntegrationTest.swift +16 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/KeychainStoreTests.swift +26 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/ProtocolModelsTests.swift +41 -7
- package/packages/RemuxKit/Tests/RemuxKitTests/RemuxStateTests.swift +20 -2
- package/scripts/setup-ci-secrets.sh +80 -0
- package/scripts/upload-testflight.sh +100 -0
- package/tests/server.test.js +1 -1
|
@@ -55,37 +55,214 @@ jobs:
|
|
|
55
55
|
tags: ${{ steps.meta.outputs.tags }}
|
|
56
56
|
labels: ${{ steps.meta.outputs.labels }}
|
|
57
57
|
|
|
58
|
-
# ──
|
|
59
|
-
|
|
58
|
+
# ── iOS → TestFlight ─────────────────────────────────
|
|
59
|
+
ios:
|
|
60
60
|
runs-on: macos-15
|
|
61
61
|
permissions:
|
|
62
|
-
contents:
|
|
62
|
+
contents: read
|
|
63
63
|
steps:
|
|
64
64
|
- uses: actions/checkout@v4
|
|
65
65
|
with:
|
|
66
66
|
submodules: recursive
|
|
67
67
|
|
|
68
|
-
- name:
|
|
69
|
-
|
|
68
|
+
- name: Set up signing
|
|
69
|
+
env:
|
|
70
|
+
CERTIFICATES_P12: ${{ secrets.APPLE_CERTIFICATES_P12 }}
|
|
71
|
+
CERTIFICATES_PASSWORD: ${{ secrets.APPLE_CERTIFICATES_PASSWORD }}
|
|
72
|
+
API_KEY_P8: ${{ secrets.APP_STORE_CONNECT_API_KEY_P8 }}
|
|
73
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
74
|
+
run: |
|
|
75
|
+
# Create temporary keychain
|
|
76
|
+
KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db"
|
|
77
|
+
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
|
|
78
|
+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
79
|
+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
|
80
|
+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
81
|
+
|
|
82
|
+
# Import certificates
|
|
83
|
+
echo "$CERTIFICATES_P12" | base64 --decode > "$RUNNER_TEMP/certs.p12"
|
|
84
|
+
security import "$RUNNER_TEMP/certs.p12" \
|
|
85
|
+
-P "$CERTIFICATES_PASSWORD" \
|
|
86
|
+
-A -t cert -f pkcs12 \
|
|
87
|
+
-k "$KEYCHAIN_PATH"
|
|
88
|
+
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
|
89
|
+
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
90
|
+
|
|
91
|
+
# Add keychain to search list
|
|
92
|
+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
|
93
|
+
|
|
94
|
+
# Install Apple WWDR G3 intermediate cert
|
|
95
|
+
curl -sO https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
|
|
96
|
+
sudo security add-certificates -k /Library/Keychains/System.keychain AppleWWDRCAG3.cer
|
|
97
|
+
|
|
98
|
+
# Set up API key
|
|
99
|
+
mkdir -p ~/.private_keys
|
|
100
|
+
echo "$API_KEY_P8" | base64 --decode > ~/.private_keys/AuthKey_${API_KEY_ID}.p8
|
|
101
|
+
|
|
102
|
+
# Verify
|
|
103
|
+
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
|
104
|
+
|
|
105
|
+
- name: Archive
|
|
106
|
+
env:
|
|
107
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
108
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
109
|
+
run: |
|
|
110
|
+
TAG="${GITHUB_REF#refs/tags/}"
|
|
111
|
+
VERSION="${TAG#v}"
|
|
112
|
+
|
|
113
|
+
xcodebuild archive \
|
|
114
|
+
-project apps/ios/Remux.xcodeproj \
|
|
115
|
+
-scheme Remux \
|
|
116
|
+
-destination 'generic/platform=iOS' \
|
|
117
|
+
-archivePath "$RUNNER_TEMP/Remux.xcarchive" \
|
|
118
|
+
-allowProvisioningUpdates \
|
|
119
|
+
-authenticationKeyPath ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
|
|
120
|
+
-authenticationKeyID "$API_KEY_ID" \
|
|
121
|
+
-authenticationKeyIssuerID "$API_ISSUER_ID" \
|
|
122
|
+
CURRENT_PROJECT_VERSION="$(git rev-list --count HEAD)" \
|
|
123
|
+
MARKETING_VERSION="$VERSION" \
|
|
124
|
+
-quiet
|
|
125
|
+
|
|
126
|
+
- name: Export IPA
|
|
127
|
+
env:
|
|
128
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
129
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
130
|
+
run: |
|
|
131
|
+
cat > "$RUNNER_TEMP/ExportOptions.plist" << 'PLIST'
|
|
132
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
133
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
134
|
+
<plist version="1.0">
|
|
135
|
+
<dict>
|
|
136
|
+
<key>method</key>
|
|
137
|
+
<string>app-store-connect</string>
|
|
138
|
+
<key>signingStyle</key>
|
|
139
|
+
<string>automatic</string>
|
|
140
|
+
<key>uploadSymbols</key>
|
|
141
|
+
<true/>
|
|
142
|
+
<key>manageAppVersionAndBuildNumber</key>
|
|
143
|
+
<true/>
|
|
144
|
+
</dict>
|
|
145
|
+
</plist>
|
|
146
|
+
PLIST
|
|
147
|
+
|
|
148
|
+
xcodebuild -exportArchive \
|
|
149
|
+
-archivePath "$RUNNER_TEMP/Remux.xcarchive" \
|
|
150
|
+
-exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \
|
|
151
|
+
-exportPath "$RUNNER_TEMP/export" \
|
|
152
|
+
-allowProvisioningUpdates \
|
|
153
|
+
-authenticationKeyPath ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
|
|
154
|
+
-authenticationKeyID "$API_KEY_ID" \
|
|
155
|
+
-authenticationKeyIssuerID "$API_ISSUER_ID" \
|
|
156
|
+
-quiet
|
|
157
|
+
|
|
158
|
+
- name: Upload to TestFlight
|
|
159
|
+
env:
|
|
160
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
161
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
162
|
+
run: |
|
|
163
|
+
IPA=$(find "$RUNNER_TEMP/export" -name "*.ipa" | head -1)
|
|
164
|
+
xcrun altool --upload-app --type ios \
|
|
165
|
+
--file "$IPA" \
|
|
166
|
+
--apiKey "$API_KEY_ID" \
|
|
167
|
+
--apiIssuer "$API_ISSUER_ID"
|
|
168
|
+
|
|
169
|
+
- name: Set export compliance
|
|
170
|
+
run: |
|
|
171
|
+
pip3 install PyJWT cryptography
|
|
172
|
+
python3 << 'PYEOF'
|
|
173
|
+
import jwt, time, json, urllib.request, os
|
|
174
|
+
|
|
175
|
+
with open(os.path.expanduser(f"~/.private_keys/AuthKey_{os.environ['API_KEY_ID']}.p8")) as f:
|
|
176
|
+
key = f.read()
|
|
177
|
+
token = jwt.encode(
|
|
178
|
+
{"iss": os.environ["API_ISSUER_ID"], "iat": int(time.time()),
|
|
179
|
+
"exp": int(time.time()) + 1200, "aud": "appstoreconnect-v1"},
|
|
180
|
+
key, algorithm="ES256",
|
|
181
|
+
headers={"kid": os.environ["API_KEY_ID"], "typ": "JWT"})
|
|
182
|
+
|
|
183
|
+
# Find latest build
|
|
184
|
+
req = urllib.request.Request(
|
|
185
|
+
f"https://api.appstoreconnect.apple.com/v1/builds?filter[app]=6761521429&sort=-uploadedDate&limit=1",
|
|
186
|
+
headers={"Authorization": f"Bearer {token}"})
|
|
187
|
+
data = json.loads(urllib.request.urlopen(req).read())
|
|
188
|
+
build_id = data["data"][0]["id"]
|
|
189
|
+
|
|
190
|
+
# Set usesNonExemptEncryption = false
|
|
191
|
+
body = json.dumps({"data": {"type": "builds", "id": build_id,
|
|
192
|
+
"attributes": {"usesNonExemptEncryption": False}}}).encode()
|
|
193
|
+
req = urllib.request.Request(
|
|
194
|
+
f"https://api.appstoreconnect.apple.com/v1/builds/{build_id}",
|
|
195
|
+
data=body, method="PATCH",
|
|
196
|
+
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"})
|
|
197
|
+
urllib.request.urlopen(req)
|
|
198
|
+
print(f"✓ Export compliance set for build {build_id}")
|
|
199
|
+
PYEOF
|
|
200
|
+
env:
|
|
201
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
202
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
203
|
+
|
|
204
|
+
- name: Clean up keychain
|
|
205
|
+
if: always()
|
|
206
|
+
run: security delete-keychain "$RUNNER_TEMP/signing.keychain-db" 2>/dev/null || true
|
|
207
|
+
|
|
208
|
+
# ── macOS app (.dmg) → signed + notarized + GitHub Release ──
|
|
209
|
+
# Runs on self-hosted runner (Mac Mini) which has Developer ID cert installed
|
|
210
|
+
macos:
|
|
211
|
+
runs-on: [self-hosted, remux-deploy]
|
|
212
|
+
permissions:
|
|
213
|
+
contents: write
|
|
214
|
+
steps:
|
|
215
|
+
- uses: actions/checkout@v4
|
|
70
216
|
with:
|
|
71
|
-
|
|
217
|
+
submodules: recursive
|
|
72
218
|
|
|
73
|
-
- name:
|
|
219
|
+
- name: Verify signing identity
|
|
74
220
|
run: |
|
|
75
|
-
|
|
76
|
-
zig build -Demit-xcframework=true -Dxcframework-target=native -Doptimize=ReleaseFast
|
|
221
|
+
security find-identity -v -p codesigning | grep "Developer ID Application"
|
|
77
222
|
|
|
78
|
-
- name: Build
|
|
223
|
+
- name: Build GhosttyKit xcframework (if needed)
|
|
79
224
|
run: |
|
|
80
|
-
|
|
81
|
-
|
|
225
|
+
XCFW="vendor/ghostty/macos/GhosttyKit.xcframework"
|
|
226
|
+
if [ ! -d "$XCFW" ] || [ ! -f "$XCFW/Info.plist" ]; then
|
|
227
|
+
cd vendor/ghostty
|
|
228
|
+
zig build -Demit-xcframework=true -Dxcframework-target=native -Doptimize=ReleaseFast
|
|
229
|
+
fi
|
|
82
230
|
|
|
83
|
-
- name:
|
|
231
|
+
- name: Build, sign, and notarize DMG
|
|
232
|
+
env:
|
|
233
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
234
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
235
|
+
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
84
236
|
run: |
|
|
85
237
|
TAG="${GITHUB_REF#refs/tags/}"
|
|
86
238
|
VERSION="${TAG#v}"
|
|
239
|
+
|
|
240
|
+
# Build .app and DMG
|
|
87
241
|
bash scripts/build-dmg.sh "$VERSION"
|
|
88
242
|
|
|
243
|
+
# Sign with Developer ID + hardened runtime + timestamp
|
|
244
|
+
codesign --force --deep --options runtime --timestamp \
|
|
245
|
+
--sign "Developer ID Application: Yaoshen Wang ($TEAM_ID)" \
|
|
246
|
+
"build/Remux.app"
|
|
247
|
+
|
|
248
|
+
# Rebuild DMG with signed app
|
|
249
|
+
DMG="build/Remux-${VERSION}-arm64.dmg"
|
|
250
|
+
rm -f "$DMG"
|
|
251
|
+
hdiutil create -volname "Remux" \
|
|
252
|
+
-srcfolder build/Remux.app \
|
|
253
|
+
-ov -format UDZO "$DMG"
|
|
254
|
+
codesign --force --timestamp \
|
|
255
|
+
--sign "Developer ID Application: Yaoshen Wang ($TEAM_ID)" "$DMG"
|
|
256
|
+
|
|
257
|
+
# Notarize
|
|
258
|
+
xcrun notarytool submit "$DMG" \
|
|
259
|
+
--key ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
|
|
260
|
+
--key-id "$API_KEY_ID" \
|
|
261
|
+
--issuer "$API_ISSUER_ID" \
|
|
262
|
+
--wait
|
|
263
|
+
|
|
264
|
+
xcrun stapler staple "$DMG"
|
|
265
|
+
|
|
89
266
|
- name: Upload DMG to GitHub Release
|
|
90
267
|
env:
|
|
91
268
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -93,13 +270,10 @@ jobs:
|
|
|
93
270
|
TAG="${GITHUB_REF#refs/tags/}"
|
|
94
271
|
VERSION="${TAG#v}"
|
|
95
272
|
DMG="build/Remux-${VERSION}-arm64.dmg"
|
|
273
|
+
|
|
96
274
|
if [ -f "$DMG" ]; then
|
|
97
275
|
gh release upload "$TAG" "$DMG" --clobber
|
|
98
276
|
echo "✓ Uploaded $DMG to release $TAG"
|
|
99
|
-
else
|
|
100
|
-
echo "⚠ DMG not found, uploading .app as zip"
|
|
101
|
-
cd build && zip -r "Remux-${VERSION}-arm64.zip" Remux.app
|
|
102
|
-
gh release upload "$TAG" "Remux-${VERSION}-arm64.zip" --clobber
|
|
103
277
|
fi
|
|
104
278
|
|
|
105
279
|
# ── Update Homebrew tap ─────────────────────────────
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
ECAFCC766BD77CCD21821612 /* ManualConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3AE7922D1E43AE099D1D40 /* ManualConnectView.swift */; };
|
|
22
22
|
EE0A712F52F48C1AB61D7AEC /* RemuxKit in Frameworks */ = {isa = PBXBuildFile; productRef = EF63343AE24B477D7365EE17 /* RemuxKit */; };
|
|
23
23
|
F136D905F6E9BA7F2C1816F2 /* InspectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED7C2F66BB76FB8DE51725E /* InspectView.swift */; };
|
|
24
|
+
AA1234560000000000000001 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA1234560000000000000002 /* Assets.xcassets */; };
|
|
24
25
|
/* End PBXBuildFile section */
|
|
25
26
|
|
|
26
27
|
/* Begin PBXFileReference section */
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
D548B0C91D3AABA26641D898 /* ControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = "<group>"; };
|
|
41
42
|
DBE379B0C119DDC9C8B324E5 /* NowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowView.swift; sourceTree = "<group>"; };
|
|
42
43
|
DEEEA01EB80CE2C1601F6228 /* InspectCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectCache.swift; sourceTree = "<group>"; };
|
|
44
|
+
AA1234560000000000000002 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
|
43
45
|
/* End PBXFileReference section */
|
|
44
46
|
|
|
45
47
|
/* Begin PBXFrameworksBuildPhase section */
|
|
@@ -67,6 +69,7 @@
|
|
|
67
69
|
0E70947CCFEAEFEDCCF14E35 /* Remux */ = {
|
|
68
70
|
isa = PBXGroup;
|
|
69
71
|
children = (
|
|
72
|
+
AA1234560000000000000002 /* Assets.xcassets */,
|
|
70
73
|
48C3DD287E421F1077A8923A /* MainTabView.swift */,
|
|
71
74
|
6C491FD55D84AC2BED5C5EBD /* Remux.entitlements */,
|
|
72
75
|
662FEE0E78ADAADEA731106C /* RemuxiOSApp.swift */,
|
|
@@ -174,6 +177,7 @@
|
|
|
174
177
|
buildPhases = (
|
|
175
178
|
204B591A381758166DA82963 /* Sources */,
|
|
176
179
|
03BDC461530EDD09CDB7FF29 /* Frameworks */,
|
|
180
|
+
AA1234560000000000000003 /* Resources */,
|
|
177
181
|
);
|
|
178
182
|
buildRules = (
|
|
179
183
|
);
|
|
@@ -224,6 +228,17 @@
|
|
|
224
228
|
};
|
|
225
229
|
/* End PBXProject section */
|
|
226
230
|
|
|
231
|
+
/* Begin PBXResourcesBuildPhase section */
|
|
232
|
+
AA1234560000000000000003 /* Resources */ = {
|
|
233
|
+
isa = PBXResourcesBuildPhase;
|
|
234
|
+
buildActionMask = 2147483647;
|
|
235
|
+
files = (
|
|
236
|
+
AA1234560000000000000001 /* Assets.xcassets in Resources */,
|
|
237
|
+
);
|
|
238
|
+
runOnlyForDeploymentPostprocessing = 0;
|
|
239
|
+
};
|
|
240
|
+
/* End PBXResourcesBuildPhase section */
|
|
241
|
+
|
|
227
242
|
/* Begin PBXSourcesBuildPhase section */
|
|
228
243
|
204B591A381758166DA82963 /* Sources */ = {
|
|
229
244
|
isa = PBXSourcesBuildPhase;
|
|
@@ -378,6 +393,7 @@
|
|
|
378
393
|
CODE_SIGN_ENTITLEMENTS = Sources/Remux/Remux.entitlements;
|
|
379
394
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
|
380
395
|
GENERATE_INFOPLIST_FILE = YES;
|
|
396
|
+
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
|
381
397
|
INFOPLIST_KEY_NSCameraUsageDescription = "Remux uses the camera to scan QR codes for server pairing.";
|
|
382
398
|
INFOPLIST_KEY_NSFaceIDUsageDescription = "Remux uses Face ID to protect your terminal sessions.";
|
|
383
399
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
@@ -387,6 +403,8 @@
|
|
|
387
403
|
"$(inherited)",
|
|
388
404
|
"@executable_path/Frameworks",
|
|
389
405
|
);
|
|
406
|
+
CURRENT_PROJECT_VERSION = 1;
|
|
407
|
+
MARKETING_VERSION = 0.3.8;
|
|
390
408
|
PRODUCT_BUNDLE_IDENTIFIER = com.remux.ios;
|
|
391
409
|
SDKROOT = iphoneos;
|
|
392
410
|
TARGETED_DEVICE_FAMILY = "1,2";
|
|
@@ -400,6 +418,7 @@
|
|
|
400
418
|
CODE_SIGN_ENTITLEMENTS = Sources/Remux/Remux.entitlements;
|
|
401
419
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
|
402
420
|
GENERATE_INFOPLIST_FILE = YES;
|
|
421
|
+
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
|
403
422
|
INFOPLIST_KEY_NSCameraUsageDescription = "Remux uses the camera to scan QR codes for server pairing.";
|
|
404
423
|
INFOPLIST_KEY_NSFaceIDUsageDescription = "Remux uses Face ID to protect your terminal sessions.";
|
|
405
424
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
@@ -409,6 +428,8 @@
|
|
|
409
428
|
"$(inherited)",
|
|
410
429
|
"@executable_path/Frameworks",
|
|
411
430
|
);
|
|
431
|
+
CURRENT_PROJECT_VERSION = 1;
|
|
432
|
+
MARKETING_VERSION = 0.3.8;
|
|
412
433
|
PRODUCT_BUNDLE_IDENTIFIER = com.remux.ios;
|
|
413
434
|
SDKROOT = iphoneos;
|
|
414
435
|
TARGETED_DEVICE_FAMILY = "1,2";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"images" : [
|
|
3
|
+
{ "filename" : "icon_40x40.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" },
|
|
4
|
+
{ "filename" : "icon_60x60.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" },
|
|
5
|
+
{ "filename" : "icon_58x58.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" },
|
|
6
|
+
{ "filename" : "icon_87x87.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" },
|
|
7
|
+
{ "filename" : "icon_80x80.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" },
|
|
8
|
+
{ "filename" : "icon_120x120.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" },
|
|
9
|
+
{ "filename" : "icon_120x120.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" },
|
|
10
|
+
{ "filename" : "icon_180x180.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" },
|
|
11
|
+
{ "filename" : "icon_20x20.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" },
|
|
12
|
+
{ "filename" : "icon_40x40.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" },
|
|
13
|
+
{ "filename" : "icon_29x29.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" },
|
|
14
|
+
{ "filename" : "icon_58x58.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" },
|
|
15
|
+
{ "filename" : "icon_40x40.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" },
|
|
16
|
+
{ "filename" : "icon_80x80.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" },
|
|
17
|
+
{ "filename" : "icon_76x76.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" },
|
|
18
|
+
{ "filename" : "icon_152x152.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" },
|
|
19
|
+
{ "filename" : "icon_167x167.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" },
|
|
20
|
+
{ "filename" : "icon_1024x1024.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" }
|
|
21
|
+
],
|
|
22
|
+
"info" : { "author" : "xcode", "version" : 1 }
|
|
23
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -96,9 +96,9 @@ struct RootView: View {
|
|
|
96
96
|
private func tryAutoConnect() {
|
|
97
97
|
let servers = keychain.savedServers()
|
|
98
98
|
if let server = servers.first,
|
|
99
|
-
let
|
|
99
|
+
let credential = keychain.preferredCredential(forServer: server),
|
|
100
100
|
let url = URL(string: server) {
|
|
101
|
-
state.connect(url: url, credential:
|
|
101
|
+
state.connect(url: url, credential: credential)
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -65,13 +65,13 @@ struct MeView: View {
|
|
|
65
65
|
VStack(alignment: .leading) {
|
|
66
66
|
Text(device.name ?? device.id.prefix(8).description)
|
|
67
67
|
.font(.subheadline)
|
|
68
|
-
Text(device.
|
|
68
|
+
Text(device.trust)
|
|
69
69
|
.font(.caption)
|
|
70
|
-
.foregroundStyle(device.
|
|
70
|
+
.foregroundStyle(device.trust == "trusted" ? .green : .orange)
|
|
71
71
|
}
|
|
72
72
|
Spacer()
|
|
73
73
|
if let lastSeen = device.lastSeen {
|
|
74
|
-
Text(
|
|
74
|
+
Text(Self.lastSeenFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(lastSeen) / 1000)))
|
|
75
75
|
.font(.caption2)
|
|
76
76
|
.foregroundStyle(.tertiary)
|
|
77
77
|
}
|
|
@@ -90,7 +90,7 @@ struct MeView: View {
|
|
|
90
90
|
HStack {
|
|
91
91
|
Text("Version")
|
|
92
92
|
Spacer()
|
|
93
|
-
Text(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.3.
|
|
93
|
+
Text(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.3.9")
|
|
94
94
|
.foregroundStyle(.secondary)
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -112,6 +112,13 @@ struct MeView: View {
|
|
|
112
112
|
default: "questionmark.circle"
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
|
|
116
|
+
private static let lastSeenFormatter: DateFormatter = {
|
|
117
|
+
let formatter = DateFormatter()
|
|
118
|
+
formatter.dateStyle = .short
|
|
119
|
+
formatter.timeStyle = .short
|
|
120
|
+
return formatter
|
|
121
|
+
}()
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
extension ConnectionStatus {
|
|
@@ -53,9 +53,10 @@ struct ConnectionView: View {
|
|
|
53
53
|
serverURL = server
|
|
54
54
|
if let savedToken = keychain.loadServerToken(forServer: server) {
|
|
55
55
|
token = savedToken
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
}
|
|
57
|
+
if let credential = keychain.preferredCredential(forServer: server),
|
|
58
|
+
let url = URL(string: server) {
|
|
59
|
+
state.connect(url: url, credential: credential)
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
.buttonStyle(.plain)
|
|
@@ -76,9 +77,4 @@ struct ConnectionView: View {
|
|
|
76
77
|
try? keychain.saveServerToken(token, forServer: serverURL)
|
|
77
78
|
state.connect(url: url, credential: .token(token))
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
-
private func connectWithResumeToken(server: String, resumeToken: String) {
|
|
81
|
-
guard let url = URL(string: server) else { return }
|
|
82
|
-
state.connect(url: url, credential: .resumeToken(resumeToken))
|
|
83
|
-
}
|
|
84
80
|
}
|
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
1
3
|
public struct ProtocolCapabilities: Codable, Equatable, Sendable {
|
|
2
4
|
public let envelope: Bool
|
|
3
5
|
public let inspectV2: Bool
|
|
@@ -67,6 +69,58 @@ public struct WorkspaceState: Codable, Equatable, Sendable {
|
|
|
67
69
|
public let activeTabIndex: Int
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
public struct WorkspaceSessionSummary: Codable, Equatable, Sendable {
|
|
73
|
+
public let name: String
|
|
74
|
+
public let tabs: [WorkspaceSessionTab]
|
|
75
|
+
public let createdAt: Int
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public struct WorkspaceSessionTab: Codable, Equatable, Sendable {
|
|
79
|
+
public let id: Int
|
|
80
|
+
public let title: String
|
|
81
|
+
public let ended: Bool
|
|
82
|
+
public let clients: Int
|
|
83
|
+
public let restored: Bool
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public struct ConnectedClientInfo: Codable, Equatable, Sendable {
|
|
87
|
+
public let clientId: String
|
|
88
|
+
public let role: String
|
|
89
|
+
public let session: String?
|
|
90
|
+
public let tabId: Int?
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public struct WorkspaceSnapshot: Codable, Equatable, Sendable {
|
|
94
|
+
public let sessions: [WorkspaceSessionSummary]
|
|
95
|
+
public let clients: [ConnectedClientInfo]
|
|
96
|
+
|
|
97
|
+
public init(sessions: [WorkspaceSessionSummary], clients: [ConnectedClientInfo]) {
|
|
98
|
+
self.sessions = sessions
|
|
99
|
+
self.clients = clients
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public struct AttachedPayload: Codable, Equatable, Sendable {
|
|
104
|
+
public let tabId: Int
|
|
105
|
+
public let session: String
|
|
106
|
+
public let clientId: String
|
|
107
|
+
public let role: String
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public struct ServerInspectMeta: Codable, Equatable, Sendable {
|
|
111
|
+
public let session: String
|
|
112
|
+
public let tabId: Int?
|
|
113
|
+
public let tabTitle: String
|
|
114
|
+
public let cols: Int
|
|
115
|
+
public let rows: Int
|
|
116
|
+
public let timestamp: Int
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public struct ServerInspectResult: Codable, Equatable, Sendable {
|
|
120
|
+
public let text: String
|
|
121
|
+
public let meta: ServerInspectMeta
|
|
122
|
+
}
|
|
123
|
+
|
|
70
124
|
public struct InspectHighlight: Codable, Equatable, Sendable {
|
|
71
125
|
public let start: Int
|
|
72
126
|
public let end: Int
|
|
@@ -177,6 +231,16 @@ public struct LegacyWorkspaceState: Codable, Equatable, Sendable {
|
|
|
177
231
|
public let activeTabIndex: Int
|
|
178
232
|
}
|
|
179
233
|
|
|
234
|
+
public struct CurrentWorkspaceStatePayload: Codable, Equatable, Sendable {
|
|
235
|
+
public let sessions: [WorkspaceSessionSummary]
|
|
236
|
+
public let clients: [ConnectedClientInfo]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public struct BootstrapPayload: Codable, Equatable, Sendable {
|
|
240
|
+
public let sessions: [WorkspaceSessionSummary]
|
|
241
|
+
public let clients: [ConnectedClientInfo]
|
|
242
|
+
}
|
|
243
|
+
|
|
180
244
|
public struct LegacyInspectRequest: Codable, Equatable, Sendable {
|
|
181
245
|
public let type: String
|
|
182
246
|
public let scope: String
|