Skip to content

Commit fa5b060

Browse files
committed
Preserve provider routing rules in Happ merge
1 parent fceed45 commit fa5b060

2 files changed

Lines changed: 34 additions & 9 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ All notable changes to **decoder sec.** are documented here.
1717
- In-app log console
1818
- Signed TestFlight builds
1919

20+
## [0.1.0-beta.16] — 2026-08-12
21+
22+
### Fixed
23+
- Дополнительный фикс Happ routing: catch-all правило больше не перекрывает правила провайдера
24+
- Существующие `routing.rules` из подписки сохраняются полностью; Happ-правила добавляются безопасно
25+
- Если в подписке уже есть свой catch-all (включая `balancerTag`), второй не добавляется
26+
2027
## [0.1.0-beta.15] — 2026-08-12
2128

2229
### Fixed

‎Shared/DeepLink/RoutingProfileStore.swift‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,21 @@ enum HappRoutingApplier {
221221
])
222222

223223
// Merge with existing routing instead of replacing everything.
224-
// Keep non-Happ rules so provider-specific routing logic survives.
224+
// Important: keep provider rules untouched; append Happ catch-all only at the very end.
225225
var routing = (root["routing"] as? [String: Any]) ?? [:]
226226
let existingRules = (routing["rules"] as? [[String: Any]]) ?? []
227-
let keptExisting = existingRules.filter { !looksLikeManagedHappRule($0, proxyTag: proxyTag, directTag: directTag, blockTag: blockTag) }
228-
routing["rules"] = happRules + keptExisting
227+
228+
// Keep only specific Happ rules before provider rules.
229+
// Global catch-all is appended last so it doesn't shadow provider logic.
230+
let happSpecificRules = Array(happRules.dropLast())
231+
let happCatchAll = happRules.last
232+
233+
var mergedRules = happSpecificRules + existingRules
234+
if let catchAll = happCatchAll, !existingRules.contains(where: looksLikeCatchAllRule) {
235+
mergedRules.append(catchAll)
236+
}
237+
routing["rules"] = mergedRules
238+
229239
if let domainStrategy, !domainStrategy.isEmpty {
230240
routing["domainStrategy"] = domainStrategy
231241
} else if routing["domainStrategy"] == nil {
@@ -276,13 +286,21 @@ enum HappRoutingApplier {
276286
return nil
277287
}
278288

279-
private static func looksLikeManagedHappRule(_ rule: [String: Any], proxyTag: String, directTag: String, blockTag: String) -> Bool {
289+
private static func looksLikeCatchAllRule(_ rule: [String: Any]) -> Bool {
280290
guard let type = rule["type"] as? String, type == "field" else { return false }
281-
guard let outbound = rule["outboundTag"] as? String else { return false }
282-
if outbound == proxyTag || outbound == directTag || outbound == blockTag {
283-
return true
284-
}
285-
return false
291+
292+
// Typical Xray catch-all patterns used in subscription configs.
293+
let hasDomain = (rule["domain"] as? [Any])?.isEmpty == false
294+
let hasIP = (rule["ip"] as? [Any])?.isEmpty == false
295+
let hasPort = rule["port"] != nil
296+
let hasProtocol = rule["protocol"] != nil
297+
let hasInboundTag = rule["inboundTag"] != nil
298+
let hasProcess = rule["process"] != nil
299+
300+
// If rule has no specific selectors and forwards by outbound/balancer,
301+
// it is effectively a catch-all.
302+
let hasForward = rule["outboundTag"] != nil || rule["balancerTag"] != nil
303+
return hasForward && !hasDomain && !hasIP && !hasPort && !hasProtocol && !hasInboundTag && !hasProcess
286304
}
287305

288306
private static func stringArray(_ any: Any?) -> [String] {

0 commit comments

Comments
 (0)