Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JWT authentication options, and Expo instructions #155

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 90 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,22 @@ ZendeskChat.startChat({
phone: "optional",
department: "required",
},
// The JWT authentication options. See RNZendeskChat.d.ts for alternative options.
authenticationOptions: {
jwt: "a_jwt",
},
localizedDismissButtonTitle: "Dismiss",
});
```


### Obtaining the `YOUR_ZENDESK_ACCOUNT_KEY`

To optain your zendesk account key see the instructions in [Initializing the SDK](https://api.zopim.com/web-sdk/#initializing-the-sdk) in the Zendesk SDK.

To get your account key, follow these steps:

1. In the Zendesk Chat Dashboard, click on your profile in the upper right corner and click on the 'Check Connection' option:
![status_dropdown](https://api.zopim.com/web-sdk/images/status_dropdown.png)
1. In the dialog, copy the account key value
![account_key](https://api.zopim.com/web-sdk/images/account_key.png)

1. In the Zendesk Chat Dashboard, click on your profile in the upper right corner and click on the 'Check Connection' option: ![status_dropdown](https://api.zopim.com/web-sdk/images/status_dropdown.png)
1. In the dialog, copy the account key value ![account_key](https://api.zopim.com/web-sdk/images/account_key.png)

### Styling

Expand Down Expand Up @@ -165,11 +166,11 @@ dependencies {
api group: 'com.zendesk', name: 'chat', version: '2.2.0'
api group: 'com.zendesk', name: 'messaging', version: '4.3.1'
```
also in project build.gradle

Add ```gradle
maven { url 'https://zendesk.jfrog.io/zendesk/repo' }```

also in project build.gradle

Add `gradle maven { url 'https://zendesk.jfrog.io/zendesk/repo' }`

For RN < 0.60:

```gradle
Expand All @@ -185,6 +186,85 @@ compile project(':react-native-zendesk-chat')
Chat.INSTANCE.init(mReactContext, key, appId);
```

#### Expo

This package does not support Expo Go.

##### iOS

No extra configuration is needed for this package to work with Expo.

##### Android

When utilizing this package on Android, you'll need to add the expo plugin to modify the `build.gradle` file.

1. Create a new plugin file

You can name the file whatever you like, for this example we've called it `zendesk-chat-plugin.js` and we'll keep it in the same directory as the `app.config(.js)` file.

2. Add the plugin contents to the `zendesk-chat-plugin.js` that modifies the `build.gradle`.

You can find more ingormation about [Expo plugins here](https://docs.expo.dev/config-plugins/plugins-and-mods/).

```JavaScript
// Expo config plugin for Zendesk Chat SDK
// Adds Zendesk chat support to managed expo apps using the dev client

const { withAppBuildGradle, withProjectBuildGradle, withPlugins } = require('@expo/config-plugins');
const { mergeContents } = require('@expo/config-plugins/build/utils/generateCode');

const withZendeskProjectGradleBuild = (config) =>
withProjectBuildGradle(config, (gradleConfig) => {
gradleConfig.modResults.contents = mergeContents({
tag: 'zendesk-chat-impl-gradle-build',
src: gradleConfig.modResults.contents,
newSrc: `
allprojects {
repositories {
maven { url 'https://zendesk.jfrog.io/zendesk/repo' }
}
}
`,
anchor: /dependencies/,
offset: -1,
comment: '//',
}).contents;

return gradleConfig;
});

const withZendeskGradleBuild = (config) =>
withAppBuildGradle(config, (gradleConfig) => {
gradleConfig.modResults.contents = mergeContents({
tag: 'zendesk-chat-impl-gradle-build',
src: gradleConfig.modResults.contents,
newSrc: `
implementation group: 'com.zendesk', name: 'chat', version: '3.3.6'
implementation group: 'com.zendesk', name: 'messaging', version: '5.2.5'
`,
anchor: /dependencies/,
offset: 1,
comment: '//',
}).contents;

return gradleConfig;
});

const withZendesk = (config) =>
withPlugins(config, [withZendeskProjectGradleBuild, withZendeskGradleBuild]);

module.exports = withZendesk;
```

3. Add the expo plugin to the list of plugins in your `app.config` or `app.config.js` file.

```JavaScript
plugins: [
'./plugins/zendesk-chat-plugin.js`,
/* Your other plugins */
]
```

## Contributing

- Pull Requests are encouraged!
Expand Down
47 changes: 47 additions & 0 deletions RNZendeskChat.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,44 @@ declare module "react-native-zendesk-chat" {
botAvatarDrawableId?: number;
}

interface JwtAuthenticationHttpOptions {
/**
* The endpoint on your server to send the JWT request to.
*
* The Request looks as follows:
*
* ```shell
* curl --request POST --url <tokenEndpoint> --header 'Content-Type application/json' --data '{ "externalId": <externalId> }'
* ```
*
* Your endpoint is expected to return a JSON response with the shape:
*
* ```JSON
* { "jwt": "-the-jwt-" }
* ```
*/
tokenEndpoint: string | undefiend;

/**
* The identifier sent as part of the JWT request used to identify the user
* on your server.
*/
externalId: string | undefined;

/**
* Headers to be passed along with any HTTP request made
* to the endpoint above.
*/
headers?: Record<string, string>;
}

interface JwtAuthenticationPrefetchOptions {
/**
* A prefetched JWT for the current session
*/
jwt: string;
}

/** Current default is "optional" */
type PreChatFormFieldOptionVisibility = "hidden" | "optional" | "required";

Expand Down Expand Up @@ -62,6 +100,15 @@ declare module "react-native-zendesk-chat" {
department?: PreChatFormFieldOptionVisibility;
};

/**
* JWT Authentication options. All values must be present to enable JWT authentication
*
* For details see: https://developer.zendesk.com/documentation/classic-web-widget-sdks/chat-sdk-v2/working-with-the-chat-sdk/enabling-authenticated-users-with-the-chat-sdk-/#creating-a-jwt-token
*/
authenticationOptions?:
| JwtAuthenticationHttpOptions
| JwtAuthenticationPrefetchOptions;

/**
* Configure the Chat-Bot (if any)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,14 @@ public void startChat(ReadableMap options) {
setupChatStartObserverToSetVisitorInfo();
}

ReadableMap jwtAuthenticationOptions = RNZendeskChatModule.getReadableMap(options, "authenticationOptions", "startChat");

RNZendeskJwtAuthenticationModule jwtAuthentication = new RNZendeskJwtAuthenticationModule(jwtAuthenticationOptions);

if (jwtAuthentication.canUseJwtAuth()) {
Chat.INSTANCE.setIdentity(jwtAuthentication);
}

Activity activity = getCurrentActivity();
if (activity != null) {
messagingBuilder.withEngines(ChatEngine.engine()).show(activity, chatConfig);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.taskrabbit.zendesk;

import com.facebook.react.bridge.ReadableMap;

import org.json.JSONObject;

import java.nio.charset.StandardCharsets;
import java.util.Map;

import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import zendesk.chat.JwtAuthenticator;

public class RNZendeskJwtAuthenticationModule implements JwtAuthenticator {
private String mTokenEndpoint;
private String mExternalId;
private String mPrefetchedJwt;
private ReadableMap mExtraHeaders;
private OkHttpClient mHttpClient = new OkHttpClient();

RNZendeskJwtAuthenticationModule(ReadableMap options) {
super();

String prefetchedJwt = options.getString("jwt");

if (prefetchedJwt != null) {
this.mPrefetchedJwt = prefetchedJwt;
} else {
this.mExternalId = options.getString("externalId");
this.mTokenEndpoint = options.getString("tokenEndpoint");
this.mExtraHeaders = options.getMap("headers");
}
}

@Override
public void getToken(JwtCompletion jwtCompletion) {
if (this.mPrefetchedJwt != null) {
jwtCompletion.onTokenLoaded(this.mPrefetchedJwt);
} else {
this.makeHttpRequest(jwtCompletion);
}
}

public boolean canUseJwtAuth() {
return this.mPrefetchedJwt != null || (this.mTokenEndpoint != null && this.mExternalId != null);
}

private void makeHttpRequest(JwtCompletion completion) {
RequestBody body = RequestBody.create(this.getTokenRequestJson());

Request.Builder builder = new Request.Builder()
.post(body)
.url(HttpUrl.get(this.mTokenEndpoint))
.header("Content-Type", "application/json");

this.applyCustomerHeaders(builder);

Request request = builder.build();

try {
Response response = this.mHttpClient.newCall(request).execute();

JSONObject responseObject = new JSONObject(response.body().string());

String jwt = responseObject.getString("jwt");

if (jwt != null) {
completion.onTokenLoaded(jwt);
}

completion.onError();
} catch (Exception ex) {
completion.onError();
}
}

private void applyCustomerHeaders(Request.Builder builder) {
if (this.mExtraHeaders == null) {
return;
}

for (Map.Entry<String, Object> entry: this.mExtraHeaders.toHashMap().entrySet()) {
builder.header(entry.getKey(), entry.getValue().toString());
}
}

private byte[] getTokenRequestJson() {
try {
JSONObject json = new JSONObject();

json.put("externalId", this.mExternalId);

return json.toString().getBytes(StandardCharsets.UTF_8);
} catch (Exception exception) {
return "{}".getBytes(StandardCharsets.UTF_8);
}
}
}
5 changes: 5 additions & 0 deletions ios/RNZendeskChat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
120AA3F92AC1EF9B00E04DF9 /* RNZendeskChatAuthenticationModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 120AA3F82AC1EF9B00E04DF9 /* RNZendeskChatAuthenticationModule.m */; };
B6462ECD1C603E5C0010294B /* RNZendeskChatModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B6462ECC1C603E5C0010294B /* RNZendeskChatModule.m */; };
/* End PBXBuildFile section */

Expand All @@ -23,6 +24,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
120AA3F82AC1EF9B00E04DF9 /* RNZendeskChatAuthenticationModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNZendeskChatAuthenticationModule.m; sourceTree = "<group>"; };
B6462EBF1C603E340010294B /* libRNZendeskChat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNZendeskChat.a; sourceTree = BUILT_PRODUCTS_DIR; };
B6462ECB1C603E5C0010294B /* RNZendeskChatModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNZendeskChatModule.h; sourceTree = "<group>"; };
B6462ECC1C603E5C0010294B /* RNZendeskChatModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNZendeskChatModule.m; sourceTree = "<group>"; };
Expand All @@ -42,6 +44,7 @@
B6462EB61C603E340010294B = {
isa = PBXGroup;
children = (
120AA3F82AC1EF9B00E04DF9 /* RNZendeskChatAuthenticationModule.m */,
B6462ECB1C603E5C0010294B /* RNZendeskChatModule.h */,
B6462ECC1C603E5C0010294B /* RNZendeskChatModule.m */,
B6462EC01C603E340010294B /* Products */,
Expand Down Expand Up @@ -95,6 +98,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = B6462EB61C603E340010294B;
Expand All @@ -112,6 +116,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
120AA3F92AC1EF9B00E04DF9 /* RNZendeskChatAuthenticationModule.m in Sources */,
B6462ECD1C603E5C0010294B /* RNZendeskChatModule.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
19 changes: 19 additions & 0 deletions ios/RNZendeskChatAuthenticationModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// RNZendeskChatAuthenticationModule.h
// Pods
//
// Created by Eric Adamski on 2023-09-25.
//

#ifndef RNZendeskChatAuthenticationModule_h
#define RNZendeskChatAuthenticationModule_h

#import <ChatSDK/ChatSDK.h>
#import <ChatProvidersSDK/ChatProvidersSDK.h>

@interface JwtAuthStrategy: NSObject<ZDKJWTAuthenticator>
- (id)initJWTAuthFromConfig: (NSDictionary *)options;
- (bool)canUseJwtAuth;
@end

#endif /* RNZendeskChatAuthenticationModule_h */
Loading