├── app
├── translations
│ └── en.json
├── style.css
├── app_file.html
└── zcrmsdk.js
├── plugin-manifest.json
└── README.md
/app/translations/en.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/plugin-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "service": "CATALYST",
3 | "client": {
4 | "id": "{client-id}",
5 | "scope": "ZohoCRM.modules.ALL,ZohoCRM.settings.ALL",
6 | "accounts-url": "https://accounts.zoho.com"
7 | },
8 | "file_location": "/app/app_file.html"
9 | }
10 |
--------------------------------------------------------------------------------
/app/style.css:
--------------------------------------------------------------------------------
1 | .full{
2 | position: relative;
3 | padding: 10px;
4 | width: 100%;
5 | min-height: 100%;
6 | }
7 | .left{
8 | width: 10%;
9 | height: 100%;
10 | position: relative;
11 | float: left;
12 | }
13 | .right{
14 | width: 90%;
15 | height: 100%;
16 | position: relative;
17 | float: right;;
18 | }
19 | .modules{
20 | height: 100%;
21 | }
22 | .module{
23 | height: 50px;
24 | padding: 10px;
25 | }
26 | table{
27 | border: 1;
28 | }
29 | td,th{
30 | padding: 5px;
31 | }
32 | #leadForm{
33 | padding: 10px;
34 | }
35 | #formelement{
36 | padding : 5px;
37 | }
38 |
--------------------------------------------------------------------------------
/app/app_file.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | | Last Name | Company | Email | Lead Source |
82 | | cb | Raghu Ram | Zoho | 12547 | Web |
83 |
84 |
85 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Archival Notice:
2 |
3 | This SDK is archived. You can continue to use it, but no new features or support requests will be accepted. For the new version, refer to
4 | https://www.zoho.com/crm/developer/docs/sdk/client-side/javascript-sdk.html
5 |
6 | # zcrm-js-sdk
7 |
8 | Zoho CRM offers REST APIs for communication between several clients. This SDK helps you to make API calls from the domains registered with accounts.zoho.{com/eu/com.cn}.
9 |
10 | Refer [Building Webapp](https://www.zoho.com/crm/help/developer/webapp-sdk/build-webapp.html) and [Install CLI](https://www.zoho.com/crm/help/developer/webapp-sdk/install-cli.html) using [ZET](https://www.npmjs.com/package/zoho-extension-toolkit) before proceeding further
11 |
12 |
13 |
14 |
15 | **Please follow the steps to work with JS SDK**
16 | * Register the client from CRM UI and note the client id
17 | * Create a new project using the command '**zet init**' via terminal/command line. Choose the option '**Catalyst**' and give the project name.
18 | * New folder will be created with the project name. Inside that, there will be a file **plugin\_manifest.json**. Update the client id in that file and required scopes to be used in the web app
19 | * Under the project folder, there will be another folder named '**app**'. This will act as the base.
20 | * Include the '**zcrmsdk.js**' file (available in _app_ folder) and use it in your html files.
21 | * **ZCRM.API.AUTH.getAccess()** will create a token by authenticating the user.
22 | * After the development, run the command '**zet pack**' from the project base folder and upload it in CRM UI. FYI: Only one app can be uploaded for each client. While updating with new app, old one has to be deleted. Also redirect url will be changed.
23 | * To know the redirect url, ZCRM.API.AUTH.getAccess() function has to be accessed from web app. It'll redirect to accounts.zoho.com/oauth/v2/auth along with a parameter redirect\_uri. Take that redirect\_uri and configure it in https://api-console.zoho.com/.
24 | Eg : if the redirect\_uri is "`https://99000000223015.zappscontents.com/appfiles/99000000223015/1.0/1dd62561c00429f2c4970bf4f2b4dc09142d08b6949a17a5c3388f30851ec9cf/redirect.html`"
25 | Then
26 |
27 | "**Authorized redirect URIs**" is "`https://99000000223015.zappscontents.com/appfiles/99000000223015/1.0/1dd62561c00429f2c4970bf4f2b4dc09142d08b6949a17a5c3388f30851ec9cf/redirect.html`"
28 |
29 | "**JavaScript Domain**" is "`https://99000000223015.zappscontents.com`"
30 |
31 |
32 | To test it in local machine:-
33 | * Create a redirect.html page within the app folder.
34 | * Run it using the '**zet run**' via terminal/command line.
35 | * Enter 127.0.0.1:{your_port_number} for eg. 127.0.0.1:5000 in the browser address bar and select the app_file.html
36 | * It'll redirect to accounts.zoho.com/oauth/v2/auth along with a parameter redirect\_uri. Take that redirect\_uri and configure it in https://api-console.zoho.com/.
37 | * If the page successfully redirects to the redirect.html page then the app works as intended.
38 | **Note**
39 | - If a single page uses many ajax calls at the same time and the token is not set. All the responses will be empty json object string `'{}'` . This one has to be handled for every request.
40 | - Once token is set for the first time, the page will be reloaded.
41 |
42 |
43 | ---
44 | **Object Hierarchy**
45 |
46 | \* - indicates mandatory param and input has to be passed as JSON for the functions
47 |
48 |
49 | **ZCRM**
50 | - **AUTH**
51 | - getAccess
52 | - revokeAccess
53 | - **RECORDS**
54 | - get - (\*input.module, input.params)
55 | - post - (\*input.module, \*input.body, \*headers['Content-Type'])
56 | - put - (\*input.module, \*input.body, \*headers['Content-Type'])
57 | - delete - (\*input.module, \*input.id)
58 | - getNotes - (\*input.module, \*input.id)
59 | - getRelated - (\*input.module, \*input.id, \*input.relatedModule)
60 | - getAllDeletedRecords - (\*input.module)
61 | - getRecycleBinRecords - (\*input.module)
62 | - getPermanentlyDeletedRecords - (\*input.module)
63 | - **SETTINGS**
64 | - getFields - (\*input.params, input.id)
65 | - getLayouts - (\*input.params, input.id)
66 | - getCustomViews - (\*input.params, input.id)
67 | - updateCustomViews - (\*input.params, input.id)
68 | - getModules - (input.module)
69 | - getRoles - (input.id)
70 | - getProfiles - (input.id)
71 | - getRelatedLists - (input.id)
72 | - **ACTIONS**
73 | - convert - (\*input.id, \*input.body)
74 | - **USERS**
75 | - get - (input.id)
76 | - **ORG**
77 | - get
78 | - **ATTACHMENTS**
79 | - uploadFile - (\*input.module, \*input.id, \*input.x\_file\_content)
80 | - deleteFile - (\*input.module, \*input.id, \*input.relatedId)
81 | - downloadFile - (\*input.module, \*input.id, \*input.relatedId)
82 | - uploadLink - (\*input.module, \*input.id, \*input.params)
83 | - uploadPhoto - (\*input.module, \*input.id, \*input.x\_file\_content)
84 | - downloadPhoto - (\*input.module, \*input.id)
85 | - deletePhoto - (\*input.module, \*input.id)
86 | - init
87 |
--------------------------------------------------------------------------------
/app/zcrmsdk.js:
--------------------------------------------------------------------------------
1 | var libBase, headers, HTTP_METHODS, version;
2 | version = 2;
3 | HTTP_METHODS = {
4 | GET : "GET",//No I18N
5 | POST : "POST",//No I18N
6 | PUT : "PUT",//No I18N
7 | DELETE : "DELETE"//No I18N
8 | };
9 |
10 | function promiseResponse(request) {
11 | return new Promise(function (resolve, reject) {
12 | var body, baseUrl, xhr, i, formData;
13 | libBase = localStorage.api_domain+"/crm/v"+version+"/";
14 | baseUrl = libBase + request.url;
15 |
16 | var token = ZCRM.API.AUTH.getAccess();
17 | if(token == null){
18 | return resolve('{}'); // in case of no ticket, returns empty json
19 | }
20 |
21 | if (request.params)
22 | {
23 | baseUrl = baseUrl + '?' + request.params;
24 | }
25 |
26 | xhr = new XMLHttpRequest();
27 | xhr.withCredentials = true
28 | xhr.open(request.type, baseUrl);
29 | xhr.setRequestHeader("Authorization", "Zoho-oauthtoken "+token)
30 | for (i in headers)
31 | {
32 | xhr.setRequestHeader(i, headers[i]);
33 | }
34 |
35 | if (request.download_file){
36 | xhr.responseType = "blob";//No I18N
37 | }
38 |
39 | if (request.x_file_content) {
40 | formData = new FormData();
41 | formData.append('file', request.x_file_content);//No I18N
42 | xhr.send(formData);
43 | }
44 | else{
45 | body = request.body || null;
46 | xhr.send(body);
47 | }
48 |
49 | xhr.onreadystatechange = function() {
50 | if(xhr.readyState == 4){
51 | if (xhr.status == 204)
52 | {
53 | var respObj = {
54 | "message" : "no data", //No I18N
55 | "status_code" : "204" //No I18N
56 | }
57 | resolve(JSON.stringify(respObj));
58 | }
59 | else
60 | {
61 | if (request.download_file){
62 | var filename;
63 | var disposition = xhr.getResponseHeader("Content-Disposition");//No I18N
64 | if (disposition && disposition.indexOf('attachment') !== -1) {
65 | var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
66 | var matches = filenameRegex.exec(disposition);
67 | if (matches != null && matches[1]) {
68 | filename = matches[1].replace(/['"]/g, '');
69 | filename = filename.replace('UTF-8','');
70 | }
71 | }
72 | var blob = xhr.response;
73 | var url = URL.createObjectURL(blob);
74 | var ttt = document.createElement('a');
75 | ttt.href = url;
76 | ttt.download = filename;
77 | ttt.click();
78 | }
79 | else{
80 | resolve(xhr.response);
81 | }
82 | }
83 | }
84 | }
85 | })
86 | };
87 | function createParams(parameters)
88 | {
89 | var params, key;
90 | for (key in parameters)
91 | {
92 | if (parameters.hasOwnProperty(key)) {
93 | if (params)
94 | {
95 | params = params + key + '=' + parameters[key] + '&';
96 | }
97 | else
98 | {
99 | params = key + '=' + parameters[key] + '&';
100 | }
101 | }
102 | }
103 |
104 | return params;
105 | };
106 | function constructRequestDetails(input, url, type, isModuleParam)
107 | {
108 | var requestDetails = {};
109 |
110 | requestDetails.type = type;
111 |
112 | if (input != undefined)
113 | {
114 | if (input.id)
115 | {
116 | url = url.replace("{id}", input.id);
117 | // url = url + "/" + input.id;
118 | }
119 | else
120 | {
121 | url = url.replace("/{id}", "");
122 | }
123 | if (input.params)
124 | {
125 | requestDetails.params = createParams(input.params) + (input.module && isModuleParam ? "module=" + input.module : "");//No I18N
126 | }
127 | if (!requestDetails.params && isModuleParam)
128 | {
129 | requestDetails.params = "module=" + input.module;//No I18N
130 | }
131 | if (input.body && (type == HTTP_METHODS.POST || type == HTTP_METHODS.PUT))
132 | {
133 | requestDetails.body = JSON.stringify(input.body);
134 | }
135 | if (input.x_file_content)
136 | {
137 | requestDetails.x_file_content = input.x_file_content;
138 | }
139 | if (input.download_file)
140 | {
141 | requestDetails.download_file = input.download_file;
142 | }
143 | }
144 | requestDetails.url = url;
145 |
146 | return requestDetails;
147 | };
148 | function getParameterByName(name, url)
149 | {
150 | if (!url) url = window.location.href;
151 | name = name.replace(/[\[\]]/g, "\\$&");
152 | var regex = new RegExp("[?&]" + name + "(=([^]*)|&|#|$)");
153 | var results = regex.exec(url);
154 | if (!results) return null;
155 | if (!results[2]) return '';
156 | return decodeURIComponent(results[2].replace(/\+/g, " "));
157 | }
158 | function sdk()
159 | {
160 | return {
161 | initialize : function (configuration)
162 | {
163 | if(document.getElementById("zes_client_scope") == null){
164 | var elem = document.createElement('div');
165 | elem.setAttribute("data-scope",configuration.scopes);
166 | elem.setAttribute("data-clientid",configuration.client_id);
167 | elem.setAttribute("data-accounts-url",configuration.accounts_url);
168 | elem.setAttribute("id","zes_client_scope");
169 |
170 | document.body.appendChild(elem);
171 | }
172 |
173 | var input = {};
174 | ZCRM.API.USERS.get(input).then(function(resp){
175 | });
176 | }
177 | }
178 | }
179 | function actions()
180 | {
181 |
182 | return {
183 | convert : function (input)
184 | {
185 | return promiseResponse(constructRequestDetails(input, "Leads/{id}/actions/convert", HTTP_METHODS.POST, false));//No I18N
186 | }
187 | }
188 | }
189 | function attachments()
190 | {
191 |
192 | return {
193 | uploadFile : function (input)
194 | {
195 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/Attachments", HTTP_METHODS.POST, false));//No I18N
196 | },
197 | deleteFile : function (input)
198 | {
199 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/Attachments/"+input.relatedId, HTTP_METHODS.DELETE, false));//No I18N
200 | },
201 | downloadFile : function (input)
202 | {
203 | input.download_file = true;
204 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/Attachments/"+input.relatedId, HTTP_METHODS.GET, false));//No I18N
205 | },
206 | uploadLink : function (input)
207 | {
208 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/Attachments", HTTP_METHODS.POST, false));//No I18N
209 | },
210 | uploadPhoto : function (input)
211 | {
212 | return promiseResponse(constructRequestDetails(input, input.module+ "/{id}/photo", HTTP_METHODS.POST, false));//No I18N
213 | },
214 | downloadPhoto : function (input)
215 | {
216 | input.download_file = true;
217 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}/photo", HTTP_METHODS.GET, false));//No I18N
218 | },
219 | deletePhoto : function (input)
220 | {
221 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}/photo", HTTP_METHODS.DELETE, false));//No I18N
222 | }
223 | }
224 | }
225 |
226 | function org()
227 | {
228 |
229 | return {
230 | get : function (input)
231 | {
232 | return promiseResponse(constructRequestDetails(input, "org", HTTP_METHODS.GET, true));//No I18N
233 | }
234 | }
235 | }
236 |
237 | function records()
238 | {
239 |
240 | return {
241 | get : function(input)
242 | {
243 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}", HTTP_METHODS.GET, false));//No I18N
244 | },
245 | post : function(input)
246 | {
247 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}", HTTP_METHODS.POST, false));//No I18N
248 | },
249 | put : function(input)
250 | {
251 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}", HTTP_METHODS.PUT, false));//No I18N
252 | },
253 | delete : function (input)
254 | {
255 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}", HTTP_METHODS.DELETE, false));//No I18N
256 | },
257 | getNotes : function (input)
258 | {
259 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}/Notes", HTTP_METHODS.GET, false));//No I18N
260 | },
261 | getRelated : function (input)
262 | {
263 | return promiseResponse(constructRequestDetails(input, input.module + "/{id}/"+input.relatedModule, HTTP_METHODS.GET, false));//No I18N
264 | },
265 | getAllDeletedRecords : function (input)
266 | {
267 | if (input.params)
268 | {
269 | input.params.type = "all";
270 | }
271 | else
272 | {
273 | input.params = {
274 | "type" : "all"//No I18N
275 | };
276 | }
277 |
278 | return promiseResponse(constructRequestDetails(input, input.module + "/deleted", HTTP_METHODS.GET, false));//No I18N
279 | },
280 | getRecycleBinRecords : function (input)
281 | {
282 | if (input.params)
283 | {
284 | input.type = "recycle";
285 | }
286 | else
287 | {
288 | input.params = {
289 | "type" : "recycle"//No I18N
290 | };
291 | }
292 |
293 | return promiseResponse(constructRequestDetails(input, input.module + "/deleted", HTTP_METHODS.GET, false));//No I18N
294 | },
295 | getPermanentlyDeletedRecords : function (input)
296 | {
297 | if (input.params)
298 | {
299 | input.type = "permanent";
300 | }
301 | else
302 | {
303 | input.params = {
304 | "type" : "permanent"//No I18N
305 | };
306 | }
307 |
308 | return promiseResponse(constructRequestDetails(input, input.module + "/deleted", HTTP_METHODS.GET, false));//No I18N
309 | },
310 | search : function (input)
311 | {
312 | return promiseResponse(constructRequestDetails(input, input.module + "/search", HTTP_METHODS.GET, false));//No I18N
313 | }
314 | }
315 | }
316 | function settings()
317 | {
318 |
319 | return {
320 | getFields : function (input)
321 | {
322 | return promiseResponse(constructRequestDetails(input, "settings/fields/{id}", HTTP_METHODS.GET, true));//No I18N
323 | },
324 | getLayouts : function (input)
325 | {
326 | return promiseResponse(constructRequestDetails(input, "settings/layouts/{id}", HTTP_METHODS.GET, true));//No I18N
327 | },
328 | getCustomViews : function (input)
329 | {
330 | return promiseResponse(constructRequestDetails(input, "settings/custom_views/{id}", HTTP_METHODS.GET, true));//No I18N
331 | },
332 | updateCustomViews : function (input)
333 | {
334 | return promiseResponse(constructRequestDetails(input, "settings/custom_views/{id}", HTTP_METHODS.PUT, true));//No I18N
335 | },
336 | getModules : function (input)
337 | {
338 | return promiseResponse(constructRequestDetails(input, "settings/modules" + ((input && input.module) ? "/" + input.module : ""), HTTP_METHODS.GET, false));//No I18N
339 | },
340 | getRoles : function (input)
341 | {
342 | return promiseResponse(constructRequestDetails(input, "settings/roles/{id}", HTTP_METHODS.GET, true));//No I18N
343 | },
344 | getProfiles : function (input)
345 | {
346 | return promiseResponse(constructRequestDetails(input, "settings/profiles/{id}", HTTP_METHODS.GET, true));//No I18N
347 | },
348 | getRelatedLists : function (input)
349 | {
350 | return promiseResponse(constructRequestDetails(input, "settings/related_lists/{id}", HTTP_METHODS.GET, true));//No I18N
351 | }
352 | }
353 | }
354 | function users()
355 | {
356 |
357 | return {
358 | get : function (input)
359 | {
360 | return promiseResponse(constructRequestDetails(input, "users/{id}", HTTP_METHODS.GET, true));//No I18N
361 | }
362 | }
363 | }
364 | var listener = 0;
365 | function auth()
366 | {
367 | return {
368 | getAccess : function(){
369 | if(listener == 0){
370 | window.addEventListener("storage", function(e){
371 | if(e.key === 'access_token' && e.oldValue!=e.newValue && e.oldValue == null){
372 | location.reload();
373 | }
374 | if(e.key === 'access_token'){
375 | localStorage.removeItem('__auth_process');
376 | }
377 | }, false);
378 | listener = 1;
379 | if(localStorage.getItem('__auth_process')){ localStorage.removeItem('__auth_process'); }
380 | }
381 |
382 |
383 | var valueInStore = localStorage.getItem('access_token');
384 | var token_init = localStorage.getItem('__token_init');
385 | if(token_init != null && valueInStore != null && Date.now() >= parseInt(token_init)+55*60*1000){ // check after 55 mins
386 | valueInStore = null;
387 | localStorage.removeItem('access_token');
388 | }
389 | var auth_process = localStorage.getItem('__auth_process');
390 |
391 | if (valueInStore == null && auth_process == null)
392 | {
393 | var accountsUrl =document.getElementById("zes_client_scope").getAttribute("data-accounts-url");
394 | var endPoint = "/oauth/v2/auth";
395 | var full_grant = localStorage.getItem('full_grant');
396 | if(full_grant != null && 'true' == full_grant && localStorage.getItem('__token_init') != null){
397 | endPoint += '/refresh';
398 | }
399 | var client_id = document.getElementById("zes_client_scope").getAttribute("data-clientid");
400 | var scope = document.getElementById("zes_client_scope").getAttribute("data-scope");
401 |
402 | var path = window.location.pathname;
403 | var redirect_url = window.location.origin;
404 | var pathSplit = path.split('/');
405 | var length=pathSplit.length
406 | for (var i=0;i