├── .gitignore ├── LICENSE ├── README.md └── lib.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | .stdlib 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 - 2021 Polybit Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Autocode standard library JavaScript (web) bindings 2 | 3 | Basic JavaScript (web) bindings for the Autocode standard library. 4 | 5 | Used to interface with services built using [Autocode](https://autocode.com) and 6 | the [Autocode CLI](https://github.com/acode/lib). 7 | 8 | You can utilize any service on Autocode without installing any additional 9 | dependencies, and when you've deployed services to the Autocode standard library, 10 | you have a pre-built web-based SDK — for example; 11 | 12 | ```javascript 13 | lib.yourUsername.hostStatus({name: 'Dolores Abernathy'}, (err, result) => { 14 | 15 | // handle result 16 | 17 | }); 18 | ``` 19 | 20 | To discover Autocode APIs, visit https://autocode.com/lib. To build a service, 21 | get started with [the Autocode CLI tools](https://github.com/acode/lib). 22 | 23 | ## Installation 24 | 25 | Simply save the `lib.js` file from this package anywhere in your web project, 26 | and link it in the `` element of an HTML file before any ` 31 | ``` 32 | 33 | ## Usage 34 | 35 | Here are some fictional calling examples for a user named `user` with a 36 | "hello world" service, `helloWorld`, that takes one parameter (named `name`) 37 | and is released to both a `dev` and `release` environment (with version `0.1.1`). 38 | 39 | ```javascript 40 | // Unnamed Parameters 41 | lib.user.helloWorld('world', (err, result) => {}); 42 | 43 | // Named Parameters 44 | lib.user.helloWorld({name: 'world'}, (err, result) => {}); 45 | 46 | // Environment Specified 47 | lib.user.helloWorld['@dev']('world', (err, result) => {}); 48 | 49 | // Release Version (SemVer) Specified 50 | lib.user.helloWorld['@0.1.1']('world', (err, result) => {}); 51 | 52 | // Promise 53 | lib.user.helloWorld('world') 54 | .catch(err => {}) 55 | .then(result => {}); 56 | 57 | // Async 58 | let hello = await lib.user.helloWorld('world'); 59 | 60 | // For HTTP header information, use callback-style 61 | lib.user.helloWorld('hello', (err, result, headers) => {}); 62 | ``` 63 | 64 | ## Additional Information 65 | 66 | To learn more about Autocode, visit [autocode.com](https://autocode.com) or read the 67 | [Autocode CLI documentation on GitHub](https://github.com/acode/lib). 68 | 69 | You can follow the development team on Twitter, [@AutocodeHQ](https://twitter.com/AutocodeHQ) 70 | 71 | Autocode is © 2016 - 2023 Polybit Inc. 72 | -------------------------------------------------------------------------------- /lib.js: -------------------------------------------------------------------------------- 1 | window['lib'] = (function (window) { 2 | 3 | function base64ToByteArrays(b64data, sliceSize) { 4 | sliceSize = sliceSize || 512; 5 | var byteCharacters = window.atob(b64data); 6 | var byteArrays = []; 7 | for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { 8 | var slice = byteCharacters.slice(offset, offset + sliceSize); 9 | var byteNumbers = new Array(slice.length); 10 | for (var i = 0; i < slice.length; i++) { 11 | byteNumbers[i] = slice.charCodeAt(i); 12 | } 13 | var byteArray = new Uint8Array(byteNumbers); 14 | byteArrays.push(byteArray); 15 | } 16 | return byteArrays; 17 | }; 18 | 19 | function base64ToBlob (b64data, contentType, sliceSize) { 20 | var byteArrays = base64ToByteArrays(b64data, sliceSize); 21 | contentType = contentType || 'application/octet-stream'; 22 | var blob = new Blob(byteArrays, {type: contentType}); 23 | return blob; 24 | }; 25 | 26 | function readListeners (obj) { 27 | var copyObj = typeof obj === 'object' 28 | ? Array.isArray(obj) 29 | ? {} 30 | : obj || {} 31 | : {}; 32 | var listeners = {}; 33 | Object.keys(copyObj).forEach(function (key) { 34 | if (typeof copyObj[key] === 'function') { 35 | listeners[key] = copyObj[key]; 36 | } else { 37 | listeners[key] = (function () {}); 38 | } 39 | }); 40 | return listeners; 41 | }; 42 | 43 | function SSEHandler (streamListeners, debugListeners, responseListener) { 44 | this.processing = ''; 45 | this.streamListeners = readListeners(streamListeners); 46 | this.debugListeners = readListeners(debugListeners); 47 | this.responseListeners = readListeners({'@response': responseListener}); 48 | this.events = {}; 49 | } 50 | 51 | SSEHandler.prototype.process = function (text) { 52 | var entries = []; 53 | if (text) { 54 | this.processing = this.processing + text; 55 | entries = this.processing.split('\n\n'); 56 | var lastEntry = entries.pop(); 57 | this.processing = lastEntry; 58 | } 59 | entries 60 | .filter(entry => !!entry) 61 | .forEach(entry => { 62 | var id = null; 63 | var event = 'message'; 64 | var time = new Date().toISOString(); 65 | var data = ''; 66 | var lines = entry.split('\n').map((line, i) => { 67 | var lineData = line.split(':'); 68 | var type = lineData[0]; 69 | var contents = lineData.slice(1).join(':'); 70 | if (contents[0] === ' ') { 71 | contents = contents.slice(1); 72 | } 73 | if (type === 'event' && !data) { 74 | event = contents; 75 | } else if (type === 'data') { 76 | if (data) { 77 | data = data + '\n' + contents; 78 | } else { 79 | data = contents; 80 | } 81 | } else if (type === 'id') { 82 | id = contents; 83 | var date = new Date(id.split('/')[0]); 84 | if (date.toString() !== 'Invalid Date') { 85 | time = date.toISOString(); 86 | } 87 | } 88 | }); 89 | this.events[event] = this.events[event] || []; 90 | var name = event; 91 | var value = JSON.parse(data); 92 | var eventData = { 93 | id: id, 94 | event: name, 95 | data: value, 96 | time: time, 97 | index: this.events[event].length 98 | }; 99 | this.events[event].push(eventData); 100 | if (this.streamListeners[event]) { 101 | this.streamListeners[event].call( 102 | null, 103 | name, 104 | value, 105 | eventData 106 | ); 107 | } 108 | if (this.streamListeners['*'] && !event.startsWith('@')) { 109 | this.streamListeners['*'].call( 110 | null, 111 | name, 112 | value, 113 | eventData 114 | ); 115 | } 116 | if (this.debugListeners[event]) { 117 | this.debugListeners[event].call( 118 | null, 119 | name, 120 | value, 121 | eventData 122 | ); 123 | } 124 | if (this.debugListeners['*'] && event !== '@response') { 125 | this.debugListeners['*'].call( 126 | null, 127 | name, 128 | value, 129 | eventData 130 | ); 131 | } 132 | if (this.responseListeners[event]) { 133 | this.responseListeners[event].call( 134 | null, 135 | name, 136 | value, 137 | eventData 138 | ); 139 | } 140 | }) 141 | }; 142 | 143 | function responseHandler (statusCode, headers, response, callback) { 144 | var contentType = response.type; 145 | if (contentType === 'application/json') { 146 | var reader = new FileReader(); 147 | reader.addEventListener('error', function() { 148 | return callback(new Error('Could not read response'), response, headers); 149 | }); 150 | reader.addEventListener('load', function() { 151 | var response = reader.result; 152 | if (contentType === 'application/json') { 153 | try { 154 | response = JSON.parse(response); 155 | } catch(e) { 156 | return callback(new Error('Invalid Response JSON')); 157 | } 158 | } 159 | if (((statusCode / 100) | 0) !== 2) { 160 | var message = typeof response === 'object' ? 161 | (response && response.error && response.error.message) || 'Unspecified Error' : 162 | response; 163 | var error = new Error(message); 164 | if (response.error && typeof response.error === 'object') { 165 | Object.keys(response.error).forEach(function (key) { 166 | error[key] = response.error[key]; 167 | }); 168 | } 169 | return callback(error, response, headers); 170 | } else 171 | return callback(null, response, headers); 172 | }); 173 | reader.readAsText(response); 174 | } else { 175 | return callback(null, response, headers); 176 | } 177 | } 178 | 179 | function formatBlobAsync(blob, callback) { 180 | return blob instanceof Blob ? 181 | (function (blob) { 182 | var reader = new FileReader(); 183 | reader.addEventListener('error', function (err) { 184 | return callback(err); 185 | }); 186 | reader.addEventListener('load', function() { 187 | return callback(null, {_base64: reader.result.split(',')[1]}); 188 | }); 189 | reader.readAsDataURL(blob); 190 | })(blob) : 191 | callback(null, blob); 192 | } 193 | 194 | function formatParamsObjectAsync(params, callback) { 195 | params = Object.keys(params || {}).reduce(function (obj, key) { 196 | obj[key] = params[key]; 197 | return obj; 198 | }, {}); 199 | var formattedParams = {}; 200 | if (!Object.keys(params).length) { 201 | return callback(null, formattedParams); 202 | } 203 | var error = null; 204 | var complete = function (key) { 205 | return function(err, result) { 206 | if (error || !Object.keys(params).length) { 207 | return; 208 | } 209 | if (err) { 210 | error = err; 211 | return callback(err); 212 | } 213 | delete params[key]; 214 | formattedParams[key] = result; 215 | return Object.keys(params).length || callback(null, formattedParams); 216 | }; 217 | }; 218 | Object.keys(params).forEach(function(key, i) { formatBlobAsync(params[key], complete(key)); }); 219 | }; 220 | 221 | function containsKeywords(params) { 222 | return typeof params[0] === 'object' && 223 | !Array.isArray(params[0]) && 224 | !(params[0] instanceof Blob); 225 | } 226 | 227 | function formatParams(params) { 228 | var src = params[0] || {}; 229 | var dst = {}; 230 | return Object.keys(params[0] || {}).reduce(function (dst, name) { 231 | dst[name] = src[name]; 232 | return dst; 233 | }, dst); 234 | } 235 | 236 | function parseParameters(names, params) { 237 | 238 | var callback; 239 | 240 | if (typeof params[params.length - 1] === 'function') { 241 | callback = params.pop(); 242 | } 243 | 244 | if (params.length > 1) { 245 | throw new Error('No more than one optional argument containing an object of key-value pairs expected.'); 246 | } else if (params.length && !containsKeywords(params)) { 247 | throw new Error('Argument must be an object of key-value pairs that act as function parameters.'); 248 | } 249 | 250 | return { 251 | params: formatParams(params), 252 | callback: callback 253 | }; 254 | 255 | }; 256 | 257 | function appendVersion(names, str) { 258 | return names.concat(str); 259 | } 260 | 261 | function appendPath(names, str) { 262 | if (!str.match(/^[A-Z0-9\-\_]+$/gi)) { 263 | if (str.indexOf('@') !== -1) { 264 | throw new Error(names.join('.') + ' invalid name: ' + str + ', please specify versions and environments with [@version]'); 265 | } 266 | throw new Error(names.join('.') + ' invalid name: ' + str); 267 | } 268 | return names.concat(str); 269 | } 270 | 271 | function appendLibPath(names, str) { 272 | 273 | names = names ? names.slice() : []; 274 | var defaultVersion = '@release'; 275 | 276 | if (names.length === 0 && str === '') { 277 | 278 | return names.concat(str); 279 | 280 | } else if (names.length === 0 && str.indexOf('.') !== -1) { 281 | 282 | var versionMatch = str.match(/^[^\.]+?\.[^\.]*?(\[@[^\[\]]*?\])(\.|$)/); 283 | var arr; 284 | 285 | if (versionMatch) { 286 | version = versionMatch[1]; 287 | version = version.replace(/^\[?(.*?)\]?$/, '$1'); 288 | str = str.replace(versionMatch[1], ''); 289 | arr = str.split('.'); 290 | arr = arr.slice(0, 2).concat(version, arr.slice(2)); 291 | } else { 292 | arr = str === '.' ? [''] : str.split('.'); 293 | } 294 | 295 | while (arr.length && (names = appendLibPath(names, arr.shift()))); 296 | return names; 297 | 298 | } else if (names.length === 2 && names[0] !== '') { 299 | 300 | return str[0] === '@' ? 301 | appendVersion(names, str) : 302 | appendPath(appendVersion(names, defaultVersion), str); 303 | 304 | } else { 305 | 306 | return appendPath(names, str); 307 | 308 | } 309 | 310 | } 311 | 312 | var HOST = 'api.stdlib.com'; 313 | var PORT = 443; 314 | var PATH = '/'; 315 | 316 | var LOCALENV = 'local'; 317 | var LOCALPORT = window.STDLIB_LOCAL_PORT || 8170; 318 | 319 | function executeRemote(cfg, names, params, callback) { 320 | 321 | formatParamsObjectAsync(params, function (err, params) { 322 | 323 | if (err) { 324 | return callback(err, null, {}); 325 | } 326 | 327 | cfg = cfg || {}; 328 | cfg = Object.keys(cfg).reduce(function (ncfg, key) { 329 | ncfg[key] = cfg[key]; 330 | return ncfg 331 | }, {}); 332 | cfg.host = cfg.host || HOST; 333 | cfg.port = parseInt(cfg.port || PORT) || 80; 334 | cfg.path = cfg.path || PATH; 335 | cfg.debug = !!cfg.debug; 336 | 337 | cfg.token = cfg.token || null; 338 | cfg.keys = cfg.keys || null; 339 | cfg.convert = !!cfg.convert; 340 | 341 | var pathname; 342 | if ((names[2] || '').split(':')[0] === '@' + LOCALENV) { 343 | cfg.host = 'localhost'; 344 | cfg.port = parseInt((names[2] || '').split(':')[1]) || LOCALPORT; 345 | names[2] = ''; 346 | if (cfg.port !== LOCALPORT) { 347 | pathname = names.slice(3).join('/'); 348 | } else { 349 | // It's a root server 350 | pathname = names.slice(0, 2).join('/') + names.slice(2).join('/'); 351 | } 352 | } else { 353 | cfg.host = names.slice(0, 1).concat(cfg.host).join('.'); 354 | pathname = names.slice(1, 2).join('/') + names.slice(2).join('/'); 355 | } 356 | 357 | pathname = pathname + '/'; 358 | if (params.hasOwnProperty('__path')) { 359 | if (params.__path.startsWith('/')) { 360 | params.__path = params.__path.slice(1); 361 | } 362 | pathname = pathname + params.__path; 363 | if (!pathname.endsWith('/')) { 364 | pathname = pathname + '/'; 365 | } 366 | delete params.__path; 367 | } 368 | var headers = {}; 369 | var body; 370 | 371 | if (params.hasOwnProperty('__headers')) { 372 | if ( 373 | params.__headers && 374 | typeof params.__headers === 'object' && 375 | !Array.isArray(params.__headers) && 376 | !Buffer.isBuffer(params.__headers) 377 | ) { 378 | Object.keys(params.__headers).forEach(key => { 379 | headers[key] = params.__headers[key]; 380 | }); 381 | } else { 382 | throw new Error(`Invalid headers provided: Must be an object`); 383 | } 384 | delete params.__headers; 385 | } 386 | 387 | if (params.hasOwnProperty('__providers')) { 388 | headers['X-Authorization-Providers'] = typeof params.__providers === 'string' 389 | ? params.__providers 390 | : JSON.stringify(params.__providers); 391 | delete params.__providers; 392 | } 393 | 394 | headers['Content-Type'] = 'application/json'; 395 | headers['X-Faaslang'] = 'true'; 396 | body = new Blob([JSON.stringify(params)]); 397 | 398 | cfg.token && (headers['Authorization'] = headers['Authorization'] || 'Bearer ' + cfg.token); 399 | cfg.keys && (headers['X-Authorization-Keys'] = JSON.stringify(cfg.keys)); 400 | cfg.convert && (headers['X-Convert-Strings'] = 'true'); 401 | cfg.bg && (pathname += ':bg' + (typeof cfg.bg === 'string' ? '=' + encodeURIComponent(cfg.bg) : '')); 402 | 403 | var xhr = new XMLHttpRequest(); 404 | xhr.open( 405 | 'POST', 406 | (cfg.port === 443 ? 'https' : 'http') + 407 | '://' + cfg.host + 408 | ((cfg.port === 80 || cfg.port === 443) ? '' : ':' + cfg.port) + 409 | cfg.path + pathname 410 | ); 411 | var lastResponseText = ''; 412 | var serverSentEvent = null; 413 | if ( 414 | (params._stream || params._stream === '') || 415 | (params._debug || params._debug === '') 416 | ) { 417 | serverSentEvent = new SSEHandler( 418 | params._stream, 419 | params._debug, 420 | function (name, value, eventData) { 421 | var headers = {}; 422 | var body = null; 423 | Object.keys(value.headers).forEach(function (key) { 424 | headers[key.toLowerCase()] = value.headers[key]; 425 | }); 426 | if (headers['content-type'] !== 'application/json') { 427 | var json; 428 | try { 429 | json = JSON.parse(value.body); 430 | if ( 431 | Object.keys(json).length === 1 && 432 | json._base64 433 | ) { 434 | body = base64ToBlob(json._base64, headers['content-type']) 435 | } 436 | } catch (e) { 437 | // do nothing 438 | } 439 | } 440 | if (!body) { 441 | body = new Blob([value.body], {type: headers['content-type']}); 442 | } 443 | responseHandler( 444 | value.statusCode, 445 | headers, 446 | body, 447 | callback 448 | ); 449 | } 450 | ); 451 | xhr.responseType = 'text'; 452 | } else { 453 | xhr.responseType = 'blob'; 454 | } 455 | Object.keys(headers).forEach(function (header) { 456 | xhr.setRequestHeader(header, headers[header]); 457 | }); 458 | xhr.addEventListener('readystatechange', function() { 459 | var resheaders = xhr.getAllResponseHeaders() 460 | .split('\r\n') 461 | .reduce(function (headers, line) { 462 | var key = line.split(':')[0]; 463 | var value = line.split(':').slice(1).join(':').trim(); 464 | headers[key] = value; 465 | return headers; 466 | }, {}); 467 | if (xhr.readyState === 0) { 468 | return callback(new Error('Request aborted.'), null, {}); 469 | } else if ( 470 | resheaders['content-type'] === 'text/event-stream' && 471 | serverSentEvent && 472 | ( 473 | xhr.readyState === 3 || 474 | xhr.readyState === 4 475 | ) 476 | ) { 477 | var text = xhr.responseText.slice(lastResponseText.length); 478 | lastResponseText = xhr.responseText; 479 | serverSentEvent.process(text); 480 | } else if (xhr.readyState === 4) { 481 | if (xhr.status === 0) { 482 | return callback(new Error('HTTP request returned status code 0. This usually means the request is being blocked.'), null, {}); 483 | } 484 | var response; 485 | if (xhr.responseType === 'text') { 486 | response = new Blob( 487 | [xhr.responseText], 488 | {type: resheaders['content-type']} 489 | ); 490 | } else { 491 | response = xhr.response; 492 | } 493 | responseHandler(xhr.status, resheaders, response, callback); 494 | } 495 | }); 496 | 497 | xhr.send(body); 498 | 499 | }); 500 | 501 | }; 502 | 503 | 504 | var LibGen = function (rootCfg, cfg, names) { 505 | rootCfg = Object.assign(cfg || {}, rootCfg || {}); 506 | names = names || []; 507 | var __call__ = function __call__ (s) { 508 | var args = [].slice.call(arguments); 509 | if (names.length === 0) { 510 | if (typeof args[0] === 'string') { 511 | return LibGen(rootCfg, {}, appendLibPath(names, args[0])); 512 | } else { 513 | return LibGen(rootCfg, (typeof args[0] === 'object' ? args[0] : null) || {}, names); 514 | } 515 | } else if (names.length === 1) { 516 | return LibGen(rootCfg, {keys: (typeof args[0] === 'object' ? args[0] : {})}, names); 517 | } else { 518 | var p = parseParameters(names, args); 519 | var execute = executeRemote.bind(null, cfg, names, p.params); 520 | if (p.callback) { 521 | return execute(p.callback); 522 | } else { 523 | return new Promise(function (resolve, reject) { 524 | return execute(function (err, result) { 525 | return err ? reject(err) : resolve(result); 526 | }); 527 | }); 528 | } 529 | } 530 | }; 531 | var Proxy = window.Proxy; 532 | if (!Proxy) { 533 | return __call__; 534 | } else { 535 | return new Proxy( 536 | __call__, 537 | { 538 | get: function (target, name) { 539 | return LibGen(rootCfg, {}, appendLibPath(names, name)); 540 | } 541 | } 542 | ); 543 | } 544 | }; 545 | 546 | return LibGen(); 547 | 548 | })(window); 549 | --------------------------------------------------------------------------------