pusher/pusher-websocket-java

Any pusher-websocket-java version after 2.2.8 will crash when obfuscated with Proguard or R8

julioromano opened this issue · 21 comments

What is the issue?

Any pusher-websocket-java version after 2.2.8 will crash at runtime when obfuscated with Proguard or R8 returning:

Didn't receive all the fields expected from the ChannelAuthorizer, expected an auth and shared_secret.

What is the cause?

This is due to this change: #278

This PR changes the way the JSON is deserialized and now uses POJOs.
When obfuscating code using Proguard or R8 the POJOs will be obfuscated and their field names won't match those in the JSON anymore.

What are the possible solutions?

Adding @SerializedName annotation to all POJOs deserialized fields

OR

Including appropriate Proguard rules for consumers in src/main/resources/META-INF/proguard/pusherchannels.pro like other projects are doing (e.g. https://github.com/square/okio/blob/master/okio/src/jvmMain/resources/META-INF/proguard/okio.pro )
Right now adding this single keep-all rule -keep class com.pusher.client.** { *; } does the job but of course you might want to add more strict rules to only avoid obfuscating classes used for JSON deserialization.

CC @pusher/mobile

Hi, I wasn't able to reproduce this. I have com.pusher:pusher-java-client:2.4.0 and

debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            shrinkResources true
}

but my app is able to subscribe to a private channel and receive messages in a Android 11 simulator. Someone else in #334 said that they have a related issue in Android 6, and not in higher versions, and their error message is different. What version of Android are you targeting?

I also see this issue while authenticating to private channel

  • Android 13
  • com.pusher:pusher-java-client:2.4.0
  • proguard enabled
qm.a: Didn't receive all the fields expected from the ChannelAuthorizer, expected an auth and shared_secret.
        at sm.h.P(PrivateChannelImpl.java:7)
        at sm.c.run(ChannelManager.java:6)
        at zm.b$a.run(Factory.java:2)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
        at java.lang.Thread.run(Thread.java:1012)

What version of Android are you targeting?

12L

Seeing the same issue ( authenticating to private channel) when creating a release build with R8. (AGP 7.2.2)

How are you enabling R8? I might be missing a step along with minifyEnabled true

How are you enabling R8? I might be missing a step along with minifyEnabled true

  buildTypes {
    getByName("release") {
      isMinifyEnabled = true
      proguardFiles(
        getDefaultProguardFile("proguard-android-optimize.txt"),
        "proguard-rules.pro"
      )
    }
  }

I was missing the buildTypes {...}, now mine is definitely minifying. Are you able to reproduce this in the simulator?

Yes, did you try on a release build? For some reason enabling minify on debug builds doesn't yield a minified apk.
You can double click on the .apk inside Android Studio to open it and then open its dex files to actually check if it's been minified.

You're right, even though I saw Task :app:minifyDebugWithR8 in the build log the dex files haven't actually been minified. It minified in my release build.

I finally got the release build running after jumping through all the signing hoops. If I switch between the release (which minifies) and debug variants (which doesn't), the authorization fails on my release but works fine in debug, which confirms the theory that minifyEnabled is interfering. However I couldn't find your error message Didn't receive all the fields ... in my logcat. Is the release build suppressing that error message, and how did you surface it?

I can confirm adding -keep class com.pusher.client.** { *; } also fixes it. Regardless, if there's an extra step you did to show the error message in logcat, please let me know.

ChannelAuthorizer

The app crashed during pusher channel authorization and I found that message in the stacktrace printed on the logcat.

I can confirm I have the same issue. I updated from 2.2.8 to 2.4.0 and then 2.4.1. Previously private channels were working, after the update everything works locally in debug, but once I used minified builds from Google Play all the Pusher private channels stop working and I found the same error logged in Crashlytics:

Didn't receive all the fields expected from the ChannelAuthorizer, expected an auth and shared_secret.

Getting the error was tricky, due to the fact it is a 'release mode' and minified app. However, as I had logging to Crashlytics I found it within one of the reported non-fatal errors.

For now I will just downgrade back to 2.2.8 which was working.

I upgraded a few things and I think I'm closer to your error message (especially since it doesn't happen if minifying is disabled):

java.lang.RuntimeException: Failed to invoke public n2.a() with no args

Nevertheless I will take a look at adding the .pro file you recommended.

I've added a /src/main/resources/META-INF/proguard/pusher.pro file to this branch.

I will see if I can get approval from engineering to merge this. I've tested it excludes pusher.client classes from R8 minifying (where my own project's proguard-rules.pro does not have the exclusion setting).

@benjamin-tang-pusher
You might wanna try a narrower rule like:
-keep @com.google.gson.annotations.SerializedName class ** { *; }
This should keep only classes annotated with @SerializedName which is what we really need to keep.

I've tried -keep @com.google.gson.annotations.SerializedName class ** { *; } and I don't see com.google.gson... as one of the non-minified classes in the release apk.

Screenshot 2022-10-17 at 18 41 30

I can see it in the debug apk.

Did it work for you, and did you have to make any other changes?

Sorry my explanation was too brief.

First you have to annotate every member of the POJO classes being serialized/deserialized with @SerializedName("theNameOfTheFieldInTheJson")

Then you add -keep @com.google.gson.annotations.SerializedName class ** { *; } to your pro guard config.

This will keep (i.e. don't obfuscate) the members of the POJOs that you have annotated with @SerializedName.

The actual SerializedName.java file will still be obfuscated but that's working as intended.

I've tried adding adding @SerializedName to another member and using -keep ... SerializedName ... but no luck (interestingly the error message changes, so it's affecting something). I'm going to leave this to our engineering for a narrower exclusion. I do agree, -keep with * is too wide so the current rule I've added to the library is temporary.

v2.4.3 tested and confirmed fixed.

@julioromano or @benjamin-tang-pusher, I believe this can be closed as it was fixed by 2.4.3 and 2.4.4 via new Proguard rules.