jitsi-meet icon indicating copy to clipboard operation
jitsi-meet copied to clipboard

fix: use Gson to stringify intent data in BroadcastEvent

Open ernest3rd opened this issue 3 years ago • 16 comments

Fixes #9676

Use Gson stringifier in BroadcastEvent, instead of toString(), for stringifying the intent extra data. This allows the transfer of hashMap data.

ernest3rd avatar Aug 06 '21 11:08 ernest3rd

Hi, thanks for your contribution! If you haven't already done so, could you please make sure you sign our CLA (https://jitsi.org/icla for individuals and https://jitsi.org/ccla for corporations)? We would unfortunately be unable to merge your patch unless we have that piece :(.

jitsi-jenkins avatar Aug 06 '21 11:08 jitsi-jenkins

Hi! I have the same problem here. One question: Shouldn't you first check that data is a HashMap before serializing to JSON instead of calling toString?

AlvaroBro avatar Oct 04 '21 12:10 AlvaroBro

Ping @tmoldovan8x8

saghul avatar Oct 04 '21 16:10 saghul

Hi @ernest3rd @AlvaroBro, Could you please describe the situation when you want to send a map of maps in an event? Maybe it's a use case we could add to the sdk.

tmoldovan8x8 avatar Oct 05 '21 07:10 tmoldovan8x8

Hi @tmoldovan8x8

I really don't know all the possible events and data associated to them so I couldn't tell.

I'm just encountering this particular case, where the PARTICIPANTS_INFO_RETRIEVED event's data is a HashMap and is being serialized with toString (BroadcastEvent.java):

public Intent buildIntent() {
        if (type != null && type.action != null) {
            Intent intent = new Intent(type.action);

            for (String key : this.data.keySet()) {
                try {
                    intent.putExtra(key, this.data.get(key).toString());
                } catch (Exception e) {
                    JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
                }
            }

            return intent;
        }

        return null;
    }

But then it is being deserialized in ParticipantsService.java, and it is expecting to deserialize a JSON string, which of course fails:

    @Override
    public void onReceive(Context context, Intent intent) {
        BroadcastEvent event = new BroadcastEvent(intent);

        switch (event.getType()) {
            case PARTICIPANTS_INFO_RETRIEVED:
                try {
                    List<ParticipantInfo> participantInfoList = new Gson().fromJson(
                        event.getData().get("participantsInfo").toString(),
                        new TypeToken<ArrayList<ParticipantInfo>>() {
                        }.getType());

                    ParticipantsInfoCallback participantsInfoCallback = this.participantsInfoCallbackMap.get(event.getData().get(REQUEST_ID).toString()).get();

                    if (participantsInfoCallback != null) {
                        participantsInfoCallback.onReceived(participantInfoList);
                        this.participantsInfoCallbackMap.remove(participantsInfoCallback);
                    }
                } catch (Exception e) {
                    JitsiMeetLogger.w(TAG + "error parsing participantsList", e);
                }

                break;
        }
    }

Because of this, the ParticipantsService wont work.

@ernest3rd proposed a fix which consists in serializing the event data (in buildIntent()) as a JSON string, but as BroadcastEvent is a generic class and I asume that it might be used for other cases, I was just asking if that fix would broke other events.

I don't know the whole project code enough to make an statement, just pointing it out.

Me, I will try to fix this problem by checking the event type before serializing data, and only if it is of type PARTICIPANTS_INFO_RETRIEVED, I will serialize it as a JSON string as @ernest3rd proposed.

Regards!

AlvaroBro avatar Oct 05 '21 09:10 AlvaroBro

To my understanding as everything is already serialized to a string it should not break anything. Gson serialization outputs strings as strings so it should work the same way for those cases. But I could be wrong.

Cheers!

ernest3rd avatar Oct 05 '21 10:10 ernest3rd

Hey @AlvaroBro, can you please send us the response you are getting in the PARTICIPANTS-INFO-RETRIEVED event? The fix here can definitely work, but it might hide another problem that also cause iOS to fail sometimes. The data should contain a participantsInfo key with an array and a requestId with a string value.

tmoldovan8x8 avatar Oct 05 '21 13:10 tmoldovan8x8

@tmoldovan8x8 , you mean what is the value of this.data.get(key) in buildIntent() method (se above)? The value there is correct, it is a HashMap that contains the keys that you mention. The problem is that it is serialized using toString, which makes it impossible to deserialize it as if it were JSON.

I implemented the same solution as @ernest3rd but with a bit more of sanity checking, just in case. With this change, in Android, it works 100% of the times.

public Intent buildIntent() {
        if (type != null && type.action != null) {
            Intent intent = new Intent(type.action);

            for (String key : this.data.keySet()) {
                try {
                    switch (getType()) {
                        case PARTICIPANTS_INFO_RETRIEVED:
                            if ("participantsInfo".equals(key)) {
                                intent.putExtra(key, new Gson().toJson(this.data.get(key)));
                                break;
                            }
                            // Intended fall down
                        default:
                            intent.putExtra(key, this.data.get(key).toString());
                            break;
                    }
                } catch (Exception e) {
                    JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
                }
            }

            return intent;
        }

        return null;
    }

Now I have a problem in iOS, where I'm seeing it fail sometimes (retrieveParticipantsInfo's completion handler not called)

AlvaroBro avatar Oct 07 '21 13:10 AlvaroBro

@AlvaroBro you mean what is the value of this.data.get(key) in buildIntent() yes, the data that is received from the native side. Actually can you paste the entire content of this.data. Normally 'participantsInfo' should be an array of ParticipantsInfo, which does work with toString, so we're trying to figure out in what situation there is a HashMap there.

tmoldovan8x8 avatar Oct 07 '21 13:10 tmoldovan8x8

@tmoldovan8x8 if you call toString on a HashMap, it surely works in the sense that it will return a String object, but it won't be a JSON string object. That is the problem. The other side expects a JSON string. The data from the native side is always correct at least when using the Android sdk.

AlvaroBro avatar Oct 07 '21 13:10 AlvaroBro

@AlvaroBro yes. What I was trying to figure out is in what situation this.data.get(key) is a hashmap and causes that problem. Normally it should be an array, which works fine.

tmoldovan8x8 avatar Oct 08 '21 06:10 tmoldovan8x8

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 08 '22 22:01 stale[bot]

I also encountered this problem, because there are spaces in the name, it cannot be parsed, and the object is converted to a string without double quotation marks

Mistake: org.json.JSONException: Unterminated object at character 39 of [{participantId=bc79ed36, name=dddd &*^&(&#(, role=none, isLocal=true}]

I used @AlvaroBro solution and it works fine

and

  @Override
   public void onReceive(Context context, Intent intent) {
       BroadcastEvent event = new BroadcastEvent(intent);

       switch (event.getType()) {
           case PARTICIPANTS_INFO_RETRIEVED:
               try {
                   JSONArray participantsInfoJSONArray= new JSONArray(event.getData().get("participantsInfo").toString());
                   List<ParticipantInfo> participantInfoList = new Gson().fromJson(
                       participantsInfoJSONArray.toString(),
                       new TypeToken<ArrayList<ParticipantInfo>>() {
                       }.getType());

                   ParticipantsInfoCallback participantsInfoCallback = this.participantsInfoCallbackMap.get(event.getData().get(REQUEST_ID).toString()).get();

                   if (participantsInfoCallback != null) {
                       participantsInfoCallback.onReceived(participantInfoList);
                       this.participantsInfoCallbackMap.remove(participantsInfoCallback);
                   }
               } catch (Exception e) {
                   JitsiMeetLogger.w(TAG + "error parsing participantsList", e);
               }

               break;
       }
   }

Solve: [{"participantId":"3febda6f","name":"dddd &*^&(&#(","role":"none","isLocal":true}]

TingHuang222324 avatar Feb 14 '22 03:02 TingHuang222324

Ping @tmoldovan8x8

saghul avatar Feb 14 '22 07:02 saghul

@TingHuang222324 i have the same problem, when the name have spaces and if I fill the avatar with a url it also fails to parse.

DevTiagoCruz avatar Feb 16 '22 15:02 DevTiagoCruz

This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Feb 15 '24 01:02 github-actions[bot]