mym0404 / react-native-kakao

🟡 Kakao SDK All-In-One Solution Supports Android, iOS, Web, New architecture, Old Architecture, Expo
https://rnkakao.dev
MIT License
70 stars 2 forks source link

[BUG]: `shareFeedTemplate` throws an error when using `{ios | android}ExecutionParams` #35

Closed lifeisegg123 closed 5 months ago

lifeisegg123 commented 5 months ago

Is there an existing issue for this?

Name, Version of @react-native-kakao/*

share, 2.2.4

Version of react-native

0.73.6

What os are you seeing the problem on?

iOS

What device types are you seeing the problem on?

Physcial Device

What architecture types of react native are you seeing the problem on?

Old Architecture(Bridge)

Version of device(android API, iOS OS version, etc...)

iOS 17.5.1

Expo App

What happened?

The shareFeedTemplate method throws an error when using {ios | android}ExecutionParams only on iOS: [Error: 올바른 포맷이 아니기 때문에 해당 데이터를 읽을 수 없습니다.]

Here’s a sample structure I pass as an argument:

{
  template: {
    content: {
      title: 'some title',
      imageUrl: 'url to some image',
      link: {
        mobileWebUrl: 'url to some mobile web url',
        webUrl: 'url to some web url',
        androidExecutionParams: {
          sample1: String(1),
          sample2: String(10),
        },
        iosExecutionParams: {
          sample1: String(1),
          sample2: String(10),
        },
      },
    },
  },
}

When I wrap the object with JSON.stringify to pass it as a string, it works fine on iOS but not on Android.

I think this may happen because the Android & Flutter Kakao SDKs accept Map<string, string> (reference), but others expect a String.

Relevant a package.json.

No response

Relevant log output

No response

Code of Conduct

mym0404 commented 5 months ago

Please attach code calls this package's api

lifeisegg123 commented 5 months ago

I've called it like this, and this happened as well when I'm using shareTextTemplate.

import { shareFeedTemplate } from '@react-native-kakao/share'

...
try {
    shareFeedTemplate({
      template: {
        content: {
          title: 'some title',
          imageUrl: 'url to some image',
          link: {
            mobileWebUrl: 'url to some mobile web url',
            webUrl: 'url to some web url',
            androidExecutionParams: {
              sample1: String(1),
              sample2: String(10),
            },
            iosExecutionParams: {
              sample1: String(1),
              sample2: String(10),
            },
          },
        },
      },
    })
} catch (e) {
  console.error(e)
}
lifeisegg123 commented 5 months ago

https://github.com/mym0404/react-native-kakao/blob/75088f1140bd7c385b6b56aa564d48dc0cd4a37b/packages/share/ios/RNCKakaoShareManager.swift#L320

I've changed function like as shown below, it works.

private func generateTemplatable(
  _ type: String,
  _ defaultTemplate: inout [String: Any?]
) throws -> Templatable {
  // Serialize specific nested keys
  serializeNestedKeys(dict: &defaultTemplate, keys: ["content", "link", "androidExecutionParams"])
  serializeNestedKeys(dict: &defaultTemplate, keys: ["content", "link", "iosExecutionParams"])

  defaultTemplate["objectType"] = type

  let data = try JSONSerialization.data(withJSONObject: defaultTemplate, options: .prettyPrinted)

  switch type {
  case "feed":
    return try SdkJSONDecoder.custom.decode(FeedTemplate.self, from: data) as Templatable
  case "list":
    return try SdkJSONDecoder.custom.decode(ListTemplate.self, from: data) as Templatable
  case "location":
    return try SdkJSONDecoder.custom.decode(LocationTemplate.self, from: data) as Templatable
  case "commerce":
    return try SdkJSONDecoder.custom.decode(CommerceTemplate.self, from: data) as Templatable
  case "text":
    return try SdkJSONDecoder.custom.decode(TextTemplate.self, from: data) as Templatable
  case "calendar":
    return try SdkJSONDecoder.custom.decode(CalendarTemplate.self, from: data) as Templatable
  default:
    throw RNCKakaoError.unknown("Unknown templateType: \(type)")
  }
}
private func serializeNestedKeys(dict: inout [String: Any?], keys: [String]) {
  var keys = keys
  guard let firstKey = keys.first else { return }

  if keys.count == 1 {
    if let value = dict[firstKey], !(value is NSNull),
       JSONSerialization.isValidJSONObject(value),
       let jsonData = try? JSONSerialization.data(withJSONObject: value, options: []),
       let jsonString = String(data: jsonData, encoding: .utf8) {
      dict[firstKey] = jsonString
    }
  } else {
    keys.removeFirst()
    if var nestedDict = dict[firstKey] as? [String: Any?] {
      serializeNestedKeys(dict: &nestedDict, keys: keys)
      dict[firstKey] = nestedDict
    }
  }
}
lifeisegg123 commented 5 months ago

update. I guess this code might be more proper. it passes string as key1=value1&key2=value2

private func generateTemplatable(
    _ type: String,
    _ defaultTemplate: inout [String: Any?]
) throws -> Templatable {
    // Use the new function to process the template
    processTemplate(&defaultTemplate)

    defaultTemplate["objectType"] = type

    let data = try JSONSerialization.data(withJSONObject: defaultTemplate, options: .prettyPrinted)

    switch type {
    case "feed":
        return try SdkJSONDecoder.custom.decode(FeedTemplate.self, from: data) as Templatable
    case "list":
        return try SdkJSONDecoder.custom.decode(ListTemplate.self, from: data) as Templatable
    case "location":
        return try SdkJSONDecoder.custom.decode(LocationTemplate.self, from: data) as Templatable
    case "commerce":
        return try SdkJSONDecoder.custom.decode(CommerceTemplate.self, from: data) as Templatable
    case "text":
        return try SdkJSONDecoder.custom.decode(TextTemplate.self, from: data) as Templatable
    case "calendar":
        return try SdkJSONDecoder.custom.decode(CalendarTemplate.self, from: data) as Templatable
    default:
        throw RNCKakaoError.unknown("Unknown templateType: \(type)")
    }
}

private func processTemplate(_ template: inout [String: Any?]) {
    let paramsToConvert = ["androidExecutionParams", "iosExecutionParams"]

    for (key, value) in template {
        if paramsToConvert.contains(key), let stringMapValue = value as? [String: String] {
            template[key] = stringMapValue.queryParameters
        } else if var nestedDict = value as? [String: Any?] {
            processTemplate(&nestedDict)
            template[key] = nestedDict
        } else if var arrayValue = value as? [[String: Any?]] {
            for i in 0..<arrayValue.count {
                processTemplate(&arrayValue[i])
            }
            template[key] = arrayValue
        } else if var arrayValue = value as? [Any?] {
            for i in 0..<arrayValue.count {
                if var nestedDict = arrayValue[i] as? [String: Any?] {
                    processTemplate(&nestedDict)
                    arrayValue[i] = nestedDict
                }
            }
            template[key] = arrayValue
        }
    }
}
YangJonghun commented 5 months ago

FYI) JSONDecoder(extends from SdkJSONDecoder.custom) references the data type of struct fields. The (android|ios)ExecutionParams property declared in the Link has String type, not Map, unlike Android. Also, documentation says that if you pass it as a String, we should pass it as a query string, so above code change is appropriate.

mym0404 commented 5 months ago

I see, thanks for the investigation. I’ll fix it soon.

Rather than parsing the NSDictionary in iOS, I’ll swap the values of these keys in JS.

mym0404 commented 5 months ago

This should be fixed in 2.2.5