ios: fallback to manual parsing of apiChats and apiChat responses (#1659)

This commit is contained in:
JRoberts
2022-12-29 18:15:19 +04:00
committed by GitHub
parent 0e6909845f
commit 6cc267689e
6 changed files with 167 additions and 7 deletions

View File

@@ -0,0 +1,53 @@
//
// CIInvalidJSONView.swift
// SimpleX (iOS)
//
// Created by JRoberts on 29.12.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct CIInvalidJSONView: View {
var json: String
@State private var showJSON = false
var body: some View {
HStack(alignment: .bottom, spacing: 0) {
Text("invalid data")
.foregroundColor(.red)
.italic()
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(Color(uiColor: .tertiarySystemGroupedBackground))
.cornerRadius(18)
.textSelection(.disabled)
.onTapGesture { showJSON = true }
.sheet(isPresented: $showJSON) {
invalidJSONView(json)
}
}
}
func invalidJSONView(_ json: String) -> some View {
VStack(alignment: .leading, spacing: 16) {
Button {
showShareSheet(items: [json])
} label: {
Image(systemName: "square.and.arrow.up")
}
.frame(maxWidth: .infinity, alignment: .trailing)
ScrollView {
Text(json)
}
}
.frame(maxHeight: .infinity)
.padding()
}
struct CIInvalidJSONView_Previews: PreviewProvider {
static var previews: some View {
CIInvalidJSONView(json: "{}")
}
}

View File

@@ -72,6 +72,7 @@ struct ChatItemContentView<Content: View>: View {
case let .sndGroupFeature(feature, preference, _): chatFeatureView(feature, preference.enable.iconColor)
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
case let .rcvGroupFeatureRejected(feature): chatFeatureView(feature, .red)
case let .invalidJSON(json): CIInvalidJSONView(json: json)
}
}

View File

@@ -31,6 +31,7 @@ struct ChatListNavLink: View {
@State private var showContactRequestDialog = false
@State private var showJoinGroupDialog = false
@State private var showContactConnectionInfo = false
@State private var showInvalidJSON = false
var body: some View {
switch chat.chatInfo {
@@ -42,6 +43,8 @@ struct ChatListNavLink: View {
contactRequestNavLink(cReq)
case let .contactConnection(cConn):
contactConnectionNavLink(cConn)
case let .invalidJSON(json):
invalidJSONPreview(json)
}
}
@@ -335,6 +338,17 @@ struct ChatListNavLink: View {
}
}
}
private func invalidJSONPreview(_ json: String) -> some View {
Text("invalid chat data")
.foregroundColor(.red)
.padding(4)
.frame(height: rowHeights[dynamicTypeSize])
.onTapGesture { showInvalidJSON = true }
.sheet(isPresented: $showInvalidJSON) {
invalidJSONView(json)
}
}
}
func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection, showError: @escaping (ErrorAlert) -> Void, success: @escaping () -> Void = {}) -> Alert {

View File

@@ -138,6 +138,7 @@
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; };
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; };
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; };
6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; };
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
@@ -361,6 +362,7 @@
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = "<group>"; };
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = "<group>"; };
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = "<group>"; };
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
@@ -708,6 +710,7 @@
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */,
644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */,
1841511920742C6E152E469F /* AnimatedImageView.swift */,
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */,
);
path = ChatItem;
sourceTree = "<group>";
@@ -1026,6 +1029,7 @@
5C9C2DA92899DA6F00CC63B1 /* NetworkAndServers.swift in Sources */,
5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */,
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */,
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */,
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */,
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */,

View File

@@ -117,28 +117,64 @@ private func fromCString(_ c: UnsafeMutablePointer<CChar>) -> String {
public func chatResponse(_ s: String) -> ChatResponse {
let d = s.data(using: .utf8)!
// TODO is there a way to do it without copying the data? e.g:
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
// TODO is there a way to do it without copying the data? e.g:
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
do {
let r = try jsonDecoder.decode(APIResponse.self, from: d)
return r.resp
} catch {
logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)")
}
var type: String?
var json: String?
if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary {
if let j1 = j["resp"] as? NSDictionary, j1.count == 1 {
type = j1.allKeys[0] as? String
if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 {
type = jResp.allKeys[0] as? String
if type == "apiChats" {
if let jApiChats = jResp["apiChats"] as? NSDictionary,
let jChats = jApiChats["chats"] as? NSArray {
let chats = jChats.map { jChat in
if let chatData = try? parseChatData(jChat) {
return chatData
}
return ChatData.invalidJSON(prettyJSON(jChat) ?? "")
}
return .apiChats(chats: chats)
}
} else if type == "apiChat" {
if let jApiChat = jResp["apiChat"] as? NSDictionary,
let jChat = jApiChat["chat"] as? NSDictionary,
let chat = try? parseChatData(jChat) {
return .apiChat(chat: chat)
}
}
}
json = prettyJSON(j)
}
return ChatResponse.response(type: type ?? "invalid", json: json ?? s)
}
func prettyJSON(_ obj: NSDictionary) -> String? {
func parseChatData(_ jChat: Any) throws -> ChatData {
let jChatDict = jChat as! NSDictionary
let chatInfo: ChatInfo = try decodeObject(jChatDict["chatInfo"]!)
let chatStats: ChatStats = try decodeObject(jChatDict["chatStats"]!)
let jChatItems = jChatDict["chatItems"] as! NSArray
let chatItems = jChatItems.map { jCI in
if let ci: ChatItem = try? decodeObject(jCI) {
return ci
}
return ChatItem.invalidJSON(prettyJSON(jCI) ?? "")
}
return ChatData(chatInfo: chatInfo, chatItems: chatItems, chatStats: chatStats)
}
func decodeObject<T: Decodable>(_ obj: Any) throws -> T {
try jsonDecoder.decode(T.self, from: JSONSerialization.data(withJSONObject: obj))
}
func prettyJSON(_ obj: Any) -> String? {
if let d = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) {
return String(decoding: d, as: UTF8.self)
}

View File

@@ -798,6 +798,9 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case group(groupInfo: GroupInfo)
case contactRequest(contactRequest: UserContactRequest)
case contactConnection(contactConnection: PendingContactConnection)
case invalidJSON(json: String)
private static let invalidChatName = NSLocalizedString("invalid chat", comment: "invalid chat data")
public var localDisplayName: String {
get {
@@ -806,6 +809,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.localDisplayName
case let .contactRequest(contactRequest): return contactRequest.localDisplayName
case let .contactConnection(contactConnection): return contactConnection.localDisplayName
case .invalidJSON: return ChatInfo.invalidChatName
}
}
}
@@ -817,6 +821,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.displayName
case let .contactRequest(contactRequest): return contactRequest.displayName
case let .contactConnection(contactConnection): return contactConnection.displayName
case .invalidJSON: return ChatInfo.invalidChatName
}
}
}
@@ -828,6 +833,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.fullName
case let .contactRequest(contactRequest): return contactRequest.fullName
case let .contactConnection(contactConnection): return contactConnection.fullName
case .invalidJSON: return ChatInfo.invalidChatName
}
}
}
@@ -839,6 +845,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.image
case let .contactRequest(contactRequest): return contactRequest.image
case let .contactConnection(contactConnection): return contactConnection.image
case .invalidJSON: return nil
}
}
}
@@ -850,6 +857,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.localAlias
case let .contactRequest(contactRequest): return contactRequest.localAlias
case let .contactConnection(contactConnection): return contactConnection.localAlias
case .invalidJSON: return ""
}
}
}
@@ -861,6 +869,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.id
case let .contactRequest(contactRequest): return contactRequest.id
case let .contactConnection(contactConnection): return contactConnection.id
case .invalidJSON: return ""
}
}
}
@@ -872,6 +881,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case .group: return .group
case .contactRequest: return .contactRequest
case .contactConnection: return .contactConnection
case .invalidJSON: return .direct
}
}
}
@@ -883,6 +893,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.apiId
case let .contactRequest(contactRequest): return contactRequest.apiId
case let .contactConnection(contactConnection): return contactConnection.apiId
case .invalidJSON: return 0
}
}
}
@@ -894,6 +905,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.ready
case let .contactRequest(contactRequest): return contactRequest.ready
case let .contactConnection(contactConnection): return contactConnection.ready
case .invalidJSON: return false
}
}
}
@@ -905,6 +917,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.sendMsgEnabled
case let .contactRequest(contactRequest): return contactRequest.sendMsgEnabled
case let .contactConnection(contactConnection): return contactConnection.sendMsgEnabled
case .invalidJSON: return false
}
}
}
@@ -916,6 +929,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.membership.memberIncognito
case .contactRequest: return false
case let .contactConnection(contactConnection): return contactConnection.incognito
case .invalidJSON: return false
}
}
}
@@ -1003,6 +1017,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.createdAt
case let .contactRequest(contactRequest): return contactRequest.createdAt
case let .contactConnection(contactConnection): return contactConnection.createdAt
case .invalidJSON: return .now
}
}
@@ -1012,6 +1027,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
case let .group(groupInfo): return groupInfo.updatedAt
case let .contactRequest(contactRequest): return contactRequest.updatedAt
case let .contactConnection(contactConnection): return contactConnection.updatedAt
case .invalidJSON: return .now
}
}
@@ -1036,6 +1052,14 @@ public struct ChatData: Decodable, Identifiable {
public var chatStats: ChatStats
public var id: ChatId { get { chatInfo.id } }
public static func invalidJSON(_ json: String) -> ChatData {
ChatData(
chatInfo: .invalidJSON(json: json),
chatItems: [],
chatStats: ChatStats()
)
}
}
public struct ChatStats: Decodable {
@@ -1714,6 +1738,7 @@ public struct ChatItem: Identifiable, Decodable {
case .sndGroupFeature: return showNtfDir
case .rcvChatFeatureRejected: return showNtfDir
case .rcvGroupFeatureRejected: return showNtfDir
case .invalidJSON: return false
}
}
@@ -1836,6 +1861,16 @@ public struct ChatItem: Identifiable, Decodable {
file: nil
)
}
public static func invalidJSON(_ json: String) -> ChatItem {
ChatItem(
chatDir: CIDirection.directSnd,
meta: CIMeta.invalidJSON,
content: .invalidJSON(json: json),
quotedItem: nil,
file: nil
)
}
}
public enum CIDirection: Decodable {
@@ -1903,6 +1938,21 @@ public struct CIMeta: Decodable {
editable: editable
)
}
public static var invalidJSON: CIMeta {
CIMeta(
itemId: 0,
itemTs: .now,
itemText: "invalid JSON",
itemStatus: .sndNew,
createdAt: .now,
updatedAt: .now,
itemDeleted: false,
itemEdited: false,
itemLive: false,
editable: false
)
}
}
public struct CITimed: Decodable {
@@ -1971,6 +2021,7 @@ public enum CIContent: Decodable, ItemContent {
case sndGroupFeature(groupFeature: GroupFeature, preference: GroupPreference, param: Int?)
case rcvChatFeatureRejected(feature: ChatFeature)
case rcvGroupFeatureRejected(groupFeature: GroupFeature)
case invalidJSON(json: String)
public var text: String {
get {
@@ -1996,6 +2047,7 @@ public enum CIContent: Decodable, ItemContent {
case let .sndGroupFeature(feature, preference, param): return CIContent.featureText(feature, preference.enable.text, param)
case let .rcvChatFeatureRejected(feature): return String.localizedStringWithFormat("%@: received, prohibited", feature.text)
case let .rcvGroupFeatureRejected(groupFeature): return String.localizedStringWithFormat("%@: received, prohibited", groupFeature.text)
case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item")
}
}
}