Using the Apps framework
Using the Apps framework
Use the ZAF SDK to interact with the Apps framework directly from your iframe. The SDK provides aZAFClient
global object that allows cross-frame communication between your app and the host Zendesk product. For details, see theZAF Client APIdoc.
Getting the SDK
Import the ZAF SDK fromhttps://static.zdassets.com/zendesk_app_framework_sdk/2.0/zaf_sdk.min.js
. Example:
<scripttype="text/javascript"src="https://static.zdassets.com/zendesk_app_framework_sdk/2.0/zaf_sdk.min.js">script>
Using the URL in your app ensures the SDK is automatically updated with the latest 2.0 patch releases, including any bug fixes. For more information, seeFramework Versions.
Working with framework APIs
Theget
,set
andinvoke
methods of aZAFClient
object provide an interface between your iframe and the framework APIs. Due to the nature of cross-frame communication, every interaction between your iframe and the framework happens asynchronously.get
,set
andinvoke
return a JavaScriptPromiseobject with the result of the call.
get
varclient=ZAFClient.init();
client.get('ticket.requester.name').then(function(data){
console.log(data);// { "ticket.requester.name": "Mikkel Svane" }
});
set
varclient=ZAFClient.init();
client.set('ticket.type','task');
invoke
varclient=ZAFClient.init();
client.invoke('comment.appendText','My printer is on fire');
Bulk calls
Theget
,set
andinvoke
methods ofZAFClient
support bulk calls.
Bulk get
varclient=ZAFClient.init();
client.get(['ticket.subject','ticket.requester.name']).then(function(data){
console.log(data);// { 'ticket.subject': 'Help, my printer is on fire', 'ticket.requester.name': 'Mikkel Svane' }
});
Bulk set
varclient=ZAFClient.init();
client.set({'ticket.subject':'Printer Overheating Incident','ticket.type':'incident'}).then(function(data){
console.log(data);// { 'ticket.subject': 'Printer Overheating Incident', 'ticket.type': 'incident' }
});
Bulk invoke
varclient=ZAFClient.init();
client.invoke({
'ticket.comment.appendText':['My printer is on fire'],
'ticketFields:priority.hide':[]
});
Error handling
If an error occurs when callingget
,set
orinvoke
with a single path, thepromise
is rejected with an error. Errors can occur in case the path is invalid or given an invalid argument.
Example:
// invalid path
client.get('nonExistentPath').then(function(data){
console.log(data);// never run
}).catch(function(error){
console.log(error.toString());// "APIUnavailable: "nonExistentPath" Could not find handler for: "nonExistentPath"
});
// invalid argument
client.set(“ticket.form.id”,-1).then(function(data){
console.log(data);// never run
}).catch(function(error){
console.log(error.toString());// Error: "ticket.form.id" Invalid Ticket Form ID
});
When making bulk calls, thepromise
is always resolved. However, if one of the paths is invalid or if the call is given invalid arguments, the framework includes anerrors
property with the failed paths as keys and their error objects as values.
Example:
// invalid path
client.get(['ticket.subject','nonExistentPath']).then(function(data){
console.log(data);
/*
{
'ticket.subject': 'Help, my printer is on fire',
'errors': {
'nonExistentPath': Error("Could not find handler for: 'nonExistentPath'")
}
}
*/
});
// invalid argument
client.set({
'ticket.subject':'Printer Overheating Incident',
“ticket.form.id”:-1
}).then(function(data){
console.log(data);
/*
{
'ticket.subject': 'Help, my printer is on fire',
'errors': {
“ticket.form.id”: Error("Invalid Ticket Form ID")
}
}
*/
});
Additional resources
For a complete list of available APIs, see the following docs:
Working with framework events
Use theon
method of aZAFClient
object to listen for events. For available events, see the listings by location in the following docs:
Example:
varclient=ZAFClient.init();
client.on('ticket.updated',function(){
handleTicketUpdated();
});
Hook Events
Hook events allow your app to hook into product events and block the completion of the event until your app responds.
If your event handler returns a promise, the UI will wait until the promise resolves. Alternatively the event handler can abort the user request by returningfalse
or a string to show as an error message. In the following example, the handler for theticket.save
hook event prevents the user from saving the ticket:
varclient=ZAFClient.init();
client.on('ticket.save',function(){
returnfalse;
});
You can register multiple hook events. However, for the event to continue processing (for example, for the ticket save to be committed), all promises returned by the event handlers must be successfully resolved. If any event handler returns a rejected promise, throws an exception, or returnsfalse
or a string, the event is aborted. If a string is returned directly or passed with the promise rejection, the string is displayed as an error message in the UI.
Example:
varclient=ZAFClient.init();
client.on('ticket.save',function(){
returnclient.get('ticket.comment.text').then(function(data){
returnfetch('https://myapi.example.org/is_polite?comment='+data['ticket.comment.text']).catch(function(){
throw'You must be more polite';
});
});
});
Hook events have a 30-second timeout period during which your app must respond. Otherwise the event will be aborted by default.
Making HTTP requests
You can use the ZAF client'srequest()
method to safely make HTTP requests, such as REST API calls, from a client-side Zendesk app. SeeMaking API requests from a Zendesk app.
Authenticating Zendesk in your server-side app
A Zendesk app can consist of a web application running on a remote server that generates and serves all the HTML loaded in an iframe in a Zendesk product. No application logic or content is stored in the Zendesk infrastructure. When the app opens in the product, Zendesk must request the initial page from the server-side app. Subsequent page requests to the server usually originate from the iframed app itself.
If you're building a server-side app, one security feature to consider is verifying that an HTTP request for the initial page originates from a legitimate Zendesk product instance.
To help you, Zendesk can include a JSON Web Token (JWT) in the request for the initial page. After receiving the request, your server-side app can check the signed token to validate that the request originated from a legitimate Zendesk product instance. This helps preventdowngrade attacks.
The signed token also contains a number of attributes (known asclaims) that your server-side app can use to look up externally stored values associated with your Zendesk Support account.
Zendesk includes the JWT token only in requests for the initial page of the app. This page is specified in thelocation
object in the app'smanifest.jsonfile. Example:
"location":{
"support":{
"ticket_sidebar":{
"signed":true,
"url":"https://myapp.example.org/"
}
}
}
Note: This feature is only available for apps that specify a page on a remote server in thelocation
object. It's not available for apps that specify a page hosted on the Zendesk infrastructure, such as "assets/iframe.html".
Enabling the JWT token in Zendesk
让Zendes亚博k包括reque JWT令牌st for the initial app page, include one of the following properties in your app'smanifest.jsonfile:
To include a JWT token for the app in all locations, add
"signedUrls": true
to the top-level manifest object. Example:{
"name":"My App",
"signedUrls":true,
"location":{...},
...
}
or
To include a JWT token for the app only in a specific location, add
"signed": true
to the named location in thelocation
object. Example:"name":"My App",
"location":{
"support":{
"ticket_sidebar":{
"signed":true,
"url":"https://myapp.example.org/"
}
}
},
汉dling the JWT token in your server-side app
Once the JWT token is enabled, Zendesk does the following:
- Changes the request method for the initial page from GET to POST
- Includes the JWT token in a field named
token
in the POST request's form data
As a result, make sure your server-side app performs the following tasks:
- 汉dles POST requests for the initial page of the app
- Gets the token from the request's form data
- Validates the JWT token
Zendesk signs the JWT token with RSA using the SHA-256 hash algorithm ("RS256" inRFC7519). Your server-side app should use a JWT client library that supports this signature algorithm. Validating that the JWT algorithm used to encode the token is RS256 helps preventdowngrade attacks. A list of popular JWT libraries is available atjwt.io.
Your JWT library will need a public key to decode the token. You can get your app's public key with theGet App Public Keyendpoint in the Zendesk REST API:
https://{subdomain}.亚博.com/api/v2/apps/{app_id}/public_key.pem
where{subdomain}
is the subdomain of your Zendesk Support instance, and{app_id}
is the ID of your app. You can get your app id with theList All Appsendpoint:
https://{subdomain}.亚博.com/api/v2/apps.json
The key is generated when an app is created in Zendesk Support and doesn't change if you update the app later.
Note: You can only get the app's public key if the app has already been created in a Zendesk Support account. So before you can modify your server-side app to validate the JWT token, you mustupload the app package(consisting only of the manifest file and any in-product branding assets) to the Zendesk Support instance. Uploading an app doesn't enable it in the user interface yet.
The following example shows one way a server-side app can handle the Zendesk JWT token. The Ruby app uses theJWT gem,Zendesk API client for Ruby, and theSinatra web framework. The code is explained after the example.
require 'jwt'
require 'sinatra'
require 'zendesk_api'
client = ZendeskAPI::Client.new do |config|
## Change these values with your credentials
config.url = 'https://mysubdomain.zendesk.com/api/v2'
config.username = '[email protected]'
config.password = 'password'
end
app_id = 101 # Set this to your App ID
rsa_public_pem = client.connection.get("apps/#{app_id}/public_key.pem").body
puts "Validating against App ID #{app_id} with public key:"
puts rsa_public_pem
rsa_public = OpenSSL::PKey::RSA.new(rsa_public_pem)
set :protection, except: :frame_options
post '/' do
decoded_token = JWT.decode params[:token], rsa_public, true, algorithm: 'RS256'
jwt_claims = decoded_token.first
user_info = client.connection.get(jwt_claims["sub"]).body
user_name = user_info["user"]["name"]
account_url = jwt_claims["iss"]
"Welcome #{user_name} from #{account_url}!"
end
Note: If running this server-side app locally (example, http
The app performs the following tasks:
Creates a REST API client with theZendesk client for Ruby:
client = ZendeskAPI::Client.new do |config|
...
end
Uses the client to make a Zendesk REST API request to get the app's public key:
rsa_public_pem = client.connection.get("apps/#{app_id}/public_key.pem").body
Uses a Sinatra url route to handle POST requests for the initial page of the app ('/'):
post '/' do
...
end
Gets the token from the request and validates it:
post '/' do
decoded_token = JWT.decode params[:token], rsa_public, true, algorithm: 'RS256'
...
For a Python example, seeSecuring the appin the "Building a server-side app" tutorial series in the Develop Help Center.
JWT claims
The JWT token contains a number of attributes (known asclaims) that your server-side app can use to look up values associated with your Zendesk Support account.
NoteJWT令牌不授权访问任何数据in the Zendesk product instance apart from that provided in the JWT claims. An external method of authentication (such as username and password, API token, or OAuth) is required to fetch further information from the Zendesk product account, and is outside the scope of this document. For more information, seeSecurity and Authenticationin the Zendesk REST API documentation.
Claim Identifier | Name | Description | Example | Reference |
---|---|---|---|---|
exp | Expiration Time | The expiration time on or after which the JWTmust notbe accepted for processing | 1466728968 | RFC7519 Section 4.1.4 |
nbf | Not Before | 智威汤逊的时间must notbe accepted for processing | 1466747798 | RFC7519 Section 4.1.5 |
iss | Issuer | The issuer of the token, in the form of the Zendesk Support account hostname | support.zendesk.com | RFC7519 Section 4.1.1 |
aud | Audience | The audience of which the token is valid for, in the form of a URI referencing the particular installation of the app which is being loaded | https |
RFC7519 Section 4.1.3 |
iat | Issued At | The time at which the JWT was issued, which can be used to determine the age of the JWT. Set by Zendesk | 1466747858 | RFC7519 Section 4.1.6 |
sub | Subject | The subject of the JWT, in the form of a URI referencing the particular user that is loading the app | https |
RFC7519 Section 4.1.2 |
cnf | Confirmation | The identity of the proof-of-possession key, in the form of an object containing the URL to the app's public key, inJSON Web Key format | {“jku”:“计画ps |
RFC7800 Section 3.1 |
qsh | Query String Hash | A SHA256 hash of the canonical request string (method&uri-path&canonical-query-string ) |
bbe6b8ce792dccd999af6be72952d37c3bb07613d05c7576c5ff1d9eeed2ebdb | Atlassian Connect Documentation |
context | App Instance Context | An object containing the context in which the app is running, including theproduct andlocation properties |
{"context": {"product": "support", "location": "ticket_sidebar"}} | N/A |
Using local storage
App assets hosted by Zendesk have their own unique url. This means apps have their ownlocal storage, which is not shared by other apps.
However, if users have different installations of the same app, the asset url will be the same for the different installations. As a result, it's good practice to scope local storage keys to each installation to prevent conflicts between different installations running on the same browser. Example:
varclient=ZAFClient.init();
functionsetKey(key,val){
returnclient.metadata().then(function(metadata){
returnlocalStorage.setItem(metadata.installationId+":"+key,val);
});
}
functiongetKey(key){
returnclient.metadata().then(function(metadata){
returnlocalStorage.getItem(metadata.installationId+":"+key);
});
}
setKey("username","agent_extraordinaire");
getKey("username").then(function(username){
console.log(username);// agent_extraordinaire
});
Messaging between locations
The framework makes it possible for your app to interact with another instance of itself running in a different app location via theinstances
API. For more information, seeinstances
.
Example
The example below demonstrates triggering an event from one instance and listening to it from another. For this example we named the eventincoming_call
. In your own code, you can choose whatever name is appropriate.
The app in this example must run in thenav_bar
andtop_bar
locations. The manifest would contain a snippet like this:
{
"location":{
"support":{
"nav_bar":"assets/nav_bar.html",
"top_bar":"assets/top_bar.html"
}
}
}
The top bar app runs the following code:
varclient=ZAFClient.init();
client.on('incoming_call',function(){
client.invoke('popover');
});
The nav bar app runs the following code:
varclient=ZAFClient.init();
vartopBarClientPromise=client.get('instances').then(function(instancesData){
varinstances=instancesData.instances;
for(varinstanceGuidininstances){
if(instances[instanceGuid].location==='top_bar'){
returnclient.instance(instanceGuid);
}
}
});
topBarClientPromise.then(function(topBarClient){
// trigger an incoming_call event on the top bar
topBarClient.trigger('incoming_call');
});
Using modal dialogs
You can show an iframe in themodallocation using theinstances.create
API. Modals are currently only available in Zendesk Support.
Note: Apps using thesigned urlsfeature must define the modal location in their manifest, specifying the url to be used.
Example
To open a modal that displays the current Wikipedia home page, use:
varclient=ZAFClient.init();
client.invoke('instances.create',{
location:'modal',
url:'https://en.m.wikipedia.org/wiki/Main_Page',
size:{// optional
width:'450px',
height:'300px'
}
}).then(function(modalContext){
// The modal is on the screen now!
varmodalClient=client.instance(modalContext['instances.create'][0].instanceGuid);
modalClient.on('modal.close',function(){
// The modal has been closed.
});
});
Note: The maximum recommended width and height are 80vw and 80vh. Scrollbars are displayed if your content is larger than the width or height of the modal.
Testing and debugging
You can use ZCLI to run and test your apps locally. Refer toTesting your Zendesk app locally.