├── .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 |

Salesforce Contacts

17 |
18 |
    19 |
  • 20 | {{contact.FirstName}} {{contact.LastName}} 21 |
  • 22 |
23 | 24 |
`, 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 | }; --------------------------------------------------------------------------------