├── .gitignore
├── app
├── contact.ts
├── boot.ts
├── contact-detail.component.ts
├── app.component.ts
└── force.ts
├── README.md
├── oauthcallback.html
├── tsconfig.json
├── package.json
├── index.html
└── npm-debug.log
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | **/*.js
4 | **/*.map
--------------------------------------------------------------------------------
/app/contact.ts:
--------------------------------------------------------------------------------
1 | export interface Contact {
2 | Id: string;
3 | FirstName: string;
4 | LastName: string;
5 | Phone: string;
6 | };
7 |
--------------------------------------------------------------------------------
/app/boot.ts:
--------------------------------------------------------------------------------
1 | import {bootstrap} from 'angular2/platform/browser'
2 | import {AppComponent} from './app.component'
3 |
4 | bootstrap(AppComponent);
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Sample App with Angular 2 and Salesforce
2 |
3 | See [this blog post](http://coenraets.org/blog/2015/12/angular2-rest-salesforce/) for details.
--------------------------------------------------------------------------------
/oauthcallback.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "module": "system",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "removeComments": false,
10 | "noImplicitAny": false
11 | },
12 | "exclude": [
13 | "node_modules"
14 | ]
15 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-quickstart",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "tsc": "tsc",
6 | "tsc:w": "tsc -w",
7 | "lite": "lite-server",
8 | "start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
9 | },
10 | "license": "ISC",
11 | "dependencies": {
12 | "angular2": "2.0.0-beta.0",
13 | "systemjs": "0.19.6",
14 | "es6-promise": "^3.0.2",
15 | "es6-shim": "^0.33.3",
16 | "reflect-metadata": "0.1.2",
17 | "rxjs": "5.0.0-beta.0",
18 | "zone.js": "0.5.10"
19 | },
20 | "devDependencies": {
21 | "concurrently": "^1.0.0",
22 | "lite-server": "^1.3.1",
23 | "typescript": "^1.7.3"
24 | }
25 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Angular 2 QuickStart
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
33 |
34 |
35 |
36 |
37 |
38 | Loading...
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/contact-detail.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from 'angular2/core';
2 |
3 | import {Contact} from './contact';
4 |
5 | @Component({
6 | selector: 'contact-detail',
7 | inputs: ['contact'],
8 | template: `
9 |
10 |
{{contact.FirstName}} {{contact.LastName}} Details
11 |
12 |
{{contact.Id}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | `,
28 | styles:[`
29 | label {display:inline-block; width:100px; padding:8px}
30 | h2 {margin-top:0; font-weight:300}
31 | input[type=text] {-webkit-appearance:none; width:150px; height:24px; padding:4px 8px; font-size:14px; line-height:1.42857143; border:1px solid #ccc; border-radius:2px;-webkit-box-shadow:none; box-shadow:none}
32 | `],
33 | })
34 |
35 | export class ContactDetailComponent {
36 | public contact: Contact;
37 | }
--------------------------------------------------------------------------------
/npm-debug.log:
--------------------------------------------------------------------------------
1 | 0 info it worked if it ends with ok
2 | 1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'run', 'tsc:w' ]
3 | 2 info using npm@2.14.2
4 | 3 info using node@v4.0.0
5 | 4 verbose run-script [ 'pretsc:w', 'tsc:w', 'posttsc:w' ]
6 | 5 info pretsc:w angular2-quickstart@1.0.0
7 | 6 info tsc:w angular2-quickstart@1.0.0
8 | 7 verbose unsafe-perm in lifecycle true
9 | 8 info angular2-quickstart@1.0.0 Failed to exec tsc:w script
10 | 9 verbose stack Error: angular2-quickstart@1.0.0 tsc:w: `tsc -w`
11 | 9 verbose stack Exit status 1
12 | 9 verbose stack at EventEmitter. (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:214:16)
13 | 9 verbose stack at emitTwo (events.js:87:13)
14 | 9 verbose stack at EventEmitter.emit (events.js:172:7)
15 | 9 verbose stack at ChildProcess. (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:24:14)
16 | 9 verbose stack at emitTwo (events.js:87:13)
17 | 9 verbose stack at ChildProcess.emit (events.js:172:7)
18 | 9 verbose stack at maybeClose (internal/child_process.js:817:16)
19 | 9 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5)
20 | 10 verbose pkgid angular2-quickstart@1.0.0
21 | 11 verbose cwd /Users/ccoenraets/Projects/angular2-salesforce
22 | 12 error Darwin 14.5.0
23 | 13 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "run" "tsc:w"
24 | 14 error node v4.0.0
25 | 15 error npm v2.14.2
26 | 16 error code ELIFECYCLE
27 | 17 error angular2-quickstart@1.0.0 tsc:w: `tsc -w`
28 | 17 error Exit status 1
29 | 18 error Failed at the angular2-quickstart@1.0.0 tsc:w script 'tsc -w'.
30 | 18 error This is most likely a problem with the angular2-quickstart package,
31 | 18 error not with npm itself.
32 | 18 error Tell the author that this fails on your system:
33 | 18 error tsc -w
34 | 18 error You can get their info via:
35 | 18 error npm owner ls angular2-quickstart
36 | 18 error There is likely additional logging output above.
37 | 19 verbose exit [ 1, true ]
38 |
--------------------------------------------------------------------------------
/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from 'angular2/core';
2 |
3 | import {Contact} from './contact';
4 | import {ContactDetailComponent} from './contact-detail.component';
5 |
6 | import * as force from './force';
7 |
8 | interface Contact {
9 | Id: string;
10 | Name: string;
11 | };
12 |
13 | @Component({
14 | selector: 'my-app',
15 | template: `
16 |
17 | `,
25 | styles:[`
26 | header {background-color:#03A9F4; padding:14px; margin-bottom:12px; box-shadow:0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)}
27 | h1 {font-weight:300}
28 | header > h1 {font-weight:300; font-size:24px; margin:0; color: #FFFFFF}
29 | .content {display:flex}
30 | .contacts {list-style-type: none; width: 220px; margin: 0 24px 0 -24px}
31 | .contacts li {padding:4px 8px; cursor:pointer}
32 | .contacts li:hover {color:#369; background-color:#EEE}
33 | .selected { background-color:#EEE; color:#369}
34 | `],
35 | directives: [ContactDetailComponent]
36 | })
37 |
38 | export class AppComponent {
39 |
40 | public contacts:Contact[];
41 | public selectedContact:Contact;
42 |
43 | constructor() {
44 |
45 | force.init({
46 | appId: "3MVG9sG9Z3Q1Rlbc4tkIx2fI3ZUDVyYt86Ypl8ZqBXTpzPbQNHxq7gpwKcN75BB.fpgHxzSWgwgRY6nVfvBUe",
47 | proxyURL: "https://dev-cors-proxy.herokuapp.com/"
48 | });
49 | force.login().then(() => {
50 | force.query("select id, firstname, lastname, phone from contact").then(result => this.contacts = (result).records);
51 | });
52 | }
53 |
54 | onSelect(contact: Contact) {
55 | this.selectedContact = contact;
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/app/force.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * ForceJS - REST toolkit for Salesforce.com
3 | * Author: Christophe Coenraets @ccoenraets
4 | * Version: 0.7.2
5 | */
6 | "use strict";
7 |
8 | let window:any = this.window;
9 |
10 | let // The login URL for the OAuth process
11 | // To override default, pass loginURL in init(props)
12 | loginURL = 'https://login.salesforce.com',
13 |
14 | // The Connected App client Id. Default app id provided - Not for production use.
15 | // This application supports http://localhost:8200/oauthcallback.html as a valid callback URL
16 | // To override default, pass appId in init(props)
17 | appId = '3MVG9fMtCkV6eLheIEZplMqWfnGlf3Y.BcWdOf1qytXo9zxgbsrUbS.ExHTgUPJeb3jZeT8NYhc.hMyznKU92',
18 |
19 | // The force.com API version to use.
20 | // To override default, pass apiVersion in init(props)
21 | apiVersion = 'v35.0',
22 |
23 | // Keep track of OAuth data (access_token, refresh_token, and instance_url)
24 | oauth,
25 |
26 | // By default we store fbtoken in sessionStorage. This can be overridden in init()
27 | tokenStore:any = {},
28 |
29 | // if page URL is http://localhost:3000/myapp/index.html, context is /myapp
30 | context = window.location.pathname.substring(0, window.location.pathname.lastIndexOf("/")),
31 |
32 | // if page URL is http://localhost:3000/myapp/index.html, serverURL is http://localhost:3000
33 | serverURL = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''),
34 |
35 | // if page URL is http://localhost:3000/myapp/index.html, baseURL is http://localhost:3000/myapp
36 | baseURL = serverURL + context,
37 |
38 | // Only required when using REST APIs in an app hosted on your own server to avoid cross domain policy issues
39 | // To override default, pass proxyURL in init(props)
40 | proxyURL = baseURL,
41 |
42 | // if page URL is http://localhost:3000/myapp/index.html, oauthCallbackURL is http://localhost:3000/myapp/oauthcallback.html
43 | // To override default, pass oauthCallbackURL in init(props)
44 | oauthCallbackURL = baseURL + '/oauthcallback.html',
45 |
46 | // Reference to the Salesforce OAuth plugin
47 | oauthPlugin,
48 |
49 | // Whether or not to use a CORS proxy. Defaults to false if app running in Cordova, in a VF page,
50 | // or using the Salesforce console. Can be overriden in init()
51 | useProxy = (window.cordova || window.SfdcApp || window.sforce) ? false : true;
52 |
53 | /*
54 | * Determines the request base URL.
55 | */
56 | let getRequestBaseURL = () => {
57 |
58 | let url;
59 |
60 | if (useProxy) {
61 | url = proxyURL;
62 | } else if (oauth.instance_url) {
63 | url = oauth.instance_url;
64 | } else {
65 | url = serverURL;
66 | }
67 |
68 | // dev friendly API: Remove trailing '/' if any so url + path concat always works
69 | if (url.slice(-1) === '/') {
70 | url = url.slice(0, -1);
71 | }
72 |
73 | return url;
74 | };
75 |
76 | let parseQueryString = queryString => {
77 | let qs = decodeURIComponent(queryString),
78 | obj = {},
79 | params = qs.split('&');
80 | params.forEach(param => {
81 | let splitter = param.split('=');
82 | obj[splitter[0]] = splitter[1];
83 | });
84 | return obj;
85 | };
86 |
87 | let toQueryString = obj => {
88 | let parts = [],
89 | i;
90 | for (i in obj) {
91 | if (obj.hasOwnProperty(i)) {
92 | parts.push(encodeURIComponent(i) + "=" + encodeURIComponent(obj[i]));
93 | }
94 | }
95 | return parts.join("&");
96 | };
97 |
98 | let refreshTokenWithPlugin = () => {
99 |
100 | return new Promise((resolve, reject) => {
101 | oauthPlugin.authenticate(
102 | function (response) {
103 | oauth.access_token = response.accessToken;
104 | tokenStore.forceOAuth = JSON.stringify(oauth);
105 | resolve();
106 | },
107 | function () {
108 | console.error('Error refreshing oauth access token using the oauth plugin');
109 | reject();
110 | }
111 | );
112 | });
113 |
114 | };
115 |
116 | let refreshTokenWithHTTPRequest = () => new Promise((resolve, reject) => {
117 |
118 | if (!oauth.refresh_token) {
119 | console.log('ERROR: refresh token does not exist');
120 | reject();
121 | return;
122 | }
123 |
124 | let xhr = new XMLHttpRequest(),
125 |
126 | params = {
127 | 'grant_type': 'refresh_token',
128 | 'refresh_token': oauth.refresh_token,
129 | 'client_id': appId
130 | },
131 |
132 | url = useProxy ? proxyURL : loginURL;
133 |
134 | url = url + '/services/oauth2/token?' + toQueryString(params);
135 |
136 | xhr.onreadystatechange = () => {
137 | if (xhr.readyState === 4) {
138 | if (xhr.status === 200) {
139 | console.log('Token refreshed');
140 | let res = JSON.parse(xhr.responseText);
141 | oauth.access_token = res.access_token;
142 | tokenStore.forceOAuth = JSON.stringify(oauth);
143 | resolve();
144 | } else {
145 | console.log('Error while trying to refresh token: ' + xhr.responseText);
146 | reject();
147 | }
148 | }
149 | };
150 |
151 | xhr.open('POST', url, true);
152 | if (!useProxy) {
153 | xhr.setRequestHeader("Target-URL", loginURL);
154 | }
155 | xhr.send();
156 |
157 | });
158 |
159 | let refreshToken = () => {
160 | if (oauthPlugin) {
161 | return refreshTokenWithPlugin();
162 | } else {
163 | return refreshTokenWithHTTPRequest();
164 | }
165 | };
166 |
167 | let joinPaths = (path1, path2) => {
168 | if (path1.charAt(path1.length - 1) !== '/') path1 = path1 + "/";
169 | if (path2.charAt(0) === '/') path2 = path2.substr(1);
170 | return path1 + path2;
171 | }
172 |
173 | /**
174 | * Initialize ForceJS
175 | * @param params
176 | * appId (optional)
177 | * loginURL (optional)
178 | * proxyURL (optional)
179 | * oauthCallbackURL (optional)
180 | * apiVersion (optional)
181 | * accessToken (optional)
182 | * instanceURL (optional)
183 | * refreshToken (optional)
184 | */
185 | export let init = params => {
186 |
187 | if (params) {
188 | appId = params.appId || appId;
189 | apiVersion = params.apiVersion || apiVersion;
190 | loginURL = params.loginURL || loginURL;
191 | oauthCallbackURL = params.oauthCallbackURL || oauthCallbackURL;
192 | proxyURL = params.proxyURL || proxyURL;
193 | useProxy = params.useProxy === undefined ? useProxy : params.useProxy;
194 |
195 | if (params.accessToken) {
196 | if (!oauth) oauth = {};
197 | oauth.access_token = params.accessToken;
198 | }
199 |
200 | if (params.instanceURL) {
201 | if (!oauth) oauth = {};
202 | oauth.instance_url = params.instanceURL;
203 | }
204 |
205 | if (params.refreshToken) {
206 | if (!oauth) oauth = {};
207 | oauth.refresh_token = params.refreshToken;
208 | }
209 | }
210 |
211 | console.log("useProxy: " + useProxy);
212 |
213 | };
214 |
215 | /**
216 | * Discard the OAuth access_token. Use this function to test the refresh token workflow.
217 | */
218 | export let discardToken = () => {
219 | delete oauth.access_token;
220 | tokenStore.forceOAuth = JSON.stringify(oauth);
221 | };
222 |
223 | /**
224 | * Login to Salesforce using OAuth. If running in a Browser, the OAuth workflow happens in a a popup window.
225 | * If running in Cordova container, it happens using the Mobile SDK 2.3+ Oauth Plugin
226 | */
227 | export let login = () => {
228 | if (window.cordova) {
229 | return loginWithPlugin();
230 | } else {
231 | return loginWithBrowser();
232 | }
233 | };
234 |
235 | export let loginWithPlugin = () => new Promise((resolve, reject) => {
236 | document.addEventListener("deviceready", () => {
237 | oauthPlugin = window.cordova.require("com.salesforce.plugin.oauth");
238 | if (!oauthPlugin) {
239 | console.error('Salesforce Mobile SDK OAuth plugin not available');
240 | reject('Salesforce Mobile SDK OAuth plugin not available');
241 | return;
242 | }
243 | oauthPlugin.getAuthCredentials(
244 | function (creds) {
245 | // Initialize ForceJS
246 | init({
247 | accessToken: creds.accessToken,
248 | instanceURL: creds.instanceUrl,
249 | refreshToken: creds.refreshToken
250 | });
251 | resolve();
252 | },
253 | function (error) {
254 | console.log(error);
255 | reject(error);
256 | }
257 | );
258 | }, false);
259 | });
260 |
261 | export let loginWithBrowser = () => new Promise((resolve, reject) => {
262 |
263 | console.log('loginURL: ' + loginURL);
264 | console.log('oauthCallbackURL: ' + oauthCallbackURL);
265 |
266 | let loginWindowURL = loginURL + '/services/oauth2/authorize?client_id=' + appId + '&redirect_uri=' + oauthCallbackURL + '&response_type=token';
267 |
268 | document.addEventListener("oauthCallback", (evt) => {
269 |
270 | let event:any = evt;
271 | // Parse the OAuth data received from Salesforce
272 | let url = event.detail,
273 | queryString,
274 | obj;
275 |
276 | if (url.indexOf("access_token=") > 0) {
277 | queryString = url.substr(url.indexOf('#') + 1);
278 | obj = parseQueryString(queryString);
279 | oauth = obj;
280 | tokenStore.forceOAuth = JSON.stringify(oauth);
281 | resolve();
282 | } else if (url.indexOf("error=") > 0) {
283 | queryString = decodeURIComponent(url.substring(url.indexOf('?') + 1));
284 | obj = parseQueryString(queryString);
285 | reject(obj);
286 | } else {
287 | reject({status: 'access_denied'});
288 | }
289 |
290 | });
291 |
292 | window.open(loginWindowURL, '_blank', 'location=no');
293 |
294 | });
295 |
296 | /**
297 | * Gets the user's ID (if logged in)
298 | * @returns {string} | undefined
299 | */
300 | export let getUserId = () => (typeof(oauth) !== 'undefined') ? oauth.id.split('/').pop() : undefined;
301 |
302 | /**
303 | * Get the OAuth data returned by the Salesforce login process
304 | */
305 | export let getOAuthResult = () => oauth;
306 |
307 | /**
308 | * Check the login status
309 | * @returns {boolean}
310 | */
311 | export let isAuthenticated = () => (oauth && oauth.access_token) ? true : false;
312 |
313 | /**
314 | * Lets you make any Salesforce REST API request.
315 | * @param obj - Request configuration object. Can include:
316 | * method: HTTP method: GET, POST, etc. Optional - Default is 'GET'
317 | * path: path in to the Salesforce endpoint - Required
318 | * params: queryString parameters as a map - Optional
319 | * data: JSON object to send in the request body - Optional
320 | */
321 | export let request = obj => new Promise((resolve, reject) => {
322 |
323 | console.log(oauth);
324 |
325 | if (!oauth || (!oauth.access_token && !oauth.refresh_token)) {
326 | reject('No access token. Please login and try again.');
327 | return;
328 | }
329 |
330 | let method = obj.method || 'GET',
331 | xhr = new XMLHttpRequest(),
332 | url = getRequestBaseURL();
333 |
334 | // dev friendly API: Add leading '/' if missing so url + path concat always works
335 | if (obj.path.charAt(0) !== '/') {
336 | obj.path = '/' + obj.path;
337 | }
338 |
339 | url = url + obj.path;
340 |
341 | if (obj.params) {
342 | url += '?' + toQueryString(obj.params);
343 | }
344 |
345 | xhr.onreadystatechange = () => {
346 | if (xhr.readyState === 4) {
347 | if (xhr.status > 199 && xhr.status < 300) {
348 | resolve(xhr.responseText ? JSON.parse(xhr.responseText) : undefined);
349 | } else if (xhr.status === 401 && oauth.refresh_token) {
350 | refreshToken()
351 | // Try again with the new token
352 | .then(() => request(obj).then(data => resolve(data)).catch(error => reject(error)))
353 | .catch(() => {
354 | console.error(xhr.responseText);
355 | let error = xhr.responseText ? JSON.parse(xhr.responseText) : {message: 'Server error while refreshing token'};
356 | reject(error);
357 | });
358 | } else {
359 | let error = xhr.responseText ? JSON.parse(xhr.responseText) : {message: 'Server error while executing request'};
360 | reject(error);
361 | }
362 | }
363 | };
364 |
365 | xhr.open(method, url, true);
366 | xhr.setRequestHeader("Accept", "application/json");
367 | xhr.setRequestHeader("Authorization", "Bearer " + oauth.access_token);
368 | if (obj.contentType) {
369 | xhr.setRequestHeader("Content-Type", obj.contentType);
370 | }
371 | if (useProxy) {
372 | xhr.setRequestHeader("Target-URL", oauth.instance_url);
373 | }
374 | xhr.send(obj.data ? JSON.stringify(obj.data) : undefined);
375 |
376 | });
377 |
378 | /**
379 | * Convenience function to execute a SOQL query
380 | * @param soql
381 | */
382 | export let query = soql => request(
383 | {
384 | path: '/services/data/' + apiVersion + '/query',
385 | params: {q: soql}
386 | }
387 | );
388 |
389 | /**
390 | * Convenience function to retrieve a single record based on its Id
391 | * @param objectName
392 | * @param id
393 | * @param fields
394 | */
395 | export let retrieve = (objectName, id, fields) => request({
396 | path: '/services/data/' + apiVersion + '/sobjects/' + objectName + '/' + id,
397 | params: fields ? {fields: fields} : undefined
398 | }
399 | );
400 |
401 | /**
402 | * Convenience function to retrieve picklist values from a SalesForce Field
403 | * @param objectName
404 | */
405 | export let getPickListValues = objectName => request({
406 | path: '/services/data/' + apiVersion + '/sobjects/' + objectName + '/describe'
407 | }
408 | );
409 |
410 | /**
411 | * Convenience function to create a new record
412 | * @param objectName
413 | * @param data
414 | */
415 | export let create = (objectName, data) => request({
416 | method: 'POST',
417 | contentType: 'application/json',
418 | path: '/services/data/' + apiVersion + '/sobjects/' + objectName + '/',
419 | data: data
420 | }
421 | );
422 |
423 | /**
424 | * Convenience function to update a record. You can either pass the sobject returned by retrieve or query or a simple JavaScript object.
425 | * @param objectName
426 | * @param data The object to update. Must include the Id field.
427 | */
428 | export let update = (objectName, data) => {
429 |
430 | let id = data.Id || data.id,
431 | fields = JSON.parse(JSON.stringify(data));
432 |
433 | delete fields.attributes;
434 | delete fields.Id;
435 | delete fields.id;
436 |
437 | return request({
438 | method: 'POST',
439 | contentType: 'application/json',
440 | path: '/services/data/' + apiVersion + '/sobjects/' + objectName + '/' + id,
441 | params: {'_HttpMethod': 'PATCH'},
442 | data: fields
443 | }
444 | );
445 | };
446 |
447 | /**
448 | * Convenience function to delete a record
449 | * @param objectName
450 | * @param id
451 | */
452 | export let del = (objectName, id) => request({
453 | method: 'DELETE',
454 | path: '/services/data/' + apiVersion + '/sobjects/' + objectName + '/' + id
455 | }
456 | );
457 |
458 | /**
459 | * Convenience function to upsert a record
460 | * @param objectName
461 | * @param externalIdField
462 | * @param externalId
463 | * @param data
464 | */
465 | export let upsert = (objectName, externalIdField, externalId, data) => request({
466 | method: 'PATCH',
467 | contentType: 'application/json',
468 | path: '/services/data/' + apiVersion + '/sobjects/' + objectName + '/' + externalIdField + '/' + externalId,
469 | data: data
470 | }
471 | );
472 |
473 | /**
474 | * Convenience function to invoke APEX REST endpoints
475 | * @param pathOrParams
476 | */
477 | export let apexrest = pathOrParams => {
478 |
479 | let params;
480 |
481 | if (pathOrParams.substring) {
482 | params = {path: pathOrParams};
483 | } else {
484 | params = pathOrParams;
485 |
486 | if (params.path.charAt(0) !== "/") {
487 | params.path = "/" + params.path;
488 | }
489 |
490 | if (params.path.substr(0, 18) !== "/services/apexrest") {
491 | params.path = "/services/apexrest" + params.path;
492 | }
493 | }
494 |
495 | return request(params);
496 | };
497 |
498 | /**
499 | * Convenience function to invoke the Chatter API
500 | * @param pathOrParams
501 | */
502 | export let chatter = pathOrParams => {
503 |
504 | let basePath = "/services/data/" + apiVersion + "/chatter";
505 | let params;
506 |
507 | if (pathOrParams && pathOrParams.substring) {
508 | params = {path: joinPaths(basePath, pathOrParams)};
509 | } else if (pathOrParams && pathOrParams.path) {
510 | params = pathOrParams;
511 | params.path = joinPaths(basePath, pathOrParams.path);
512 | } else {
513 | return new Promise((resolve, reject) => reject("You must specify a path for the request"));
514 | }
515 |
516 | return request(params);
517 |
518 | };
--------------------------------------------------------------------------------