├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── 1.bundle.js ├── 1.bundle.js.map ├── 2.bundle.js ├── 2.bundle.js.map ├── EasybaseProvider │ ├── EasybaseProvider.d.ts │ ├── auth.d.ts │ ├── db.d.ts │ ├── g.d.ts │ ├── object-observer.d.ts │ ├── table.d.ts │ ├── types.d.ts │ └── utils.d.ts ├── bundle.js ├── bundle.js.map ├── index.d.ts ├── index.js ├── index.js.map ├── index.modern.js ├── index.modern.js.map └── restTypes.d.ts ├── package.json ├── src ├── EasybaseProvider │ ├── EasybaseProvider.ts │ ├── assets │ │ ├── image-extensions.json │ │ └── video-extensions.json │ ├── auth.ts │ ├── db.ts │ ├── g.ts │ ├── object-observer.js │ ├── table.ts │ ├── types.ts │ └── utils.ts ├── index.ts └── restTypes.ts ├── test ├── db.js ├── index.js ├── node_modules │ └── easybasejs └── package.json ├── tsconfig.json └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2020": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": 2020, 14 | "sourceType": "module" 15 | }, 16 | "plugins": [ 17 | "@typescript-eslint" 18 | ], 19 | "rules": { 20 | "space-before-function-paren": 0, 21 | "import/export": 0, 22 | "indent": 0, 23 | "semi": 0, 24 | "quotes": 0, 25 | "no-trailing-spaces": 0, 26 | "no-unused-vars": 0, 27 | "@typescript-eslint/no-namespace": 0, 28 | "@typescript-eslint/ban-types": 0, 29 | "@typescript-eslint/no-empty-function": 0 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | node_modules/* 3 | package-lock.json 4 | ebconfig.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Easybase.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | Logo 25 | 26 |

27 | 28 |
29 | 30 |

31 | npm 32 | GitHub 33 | npm bundle size 34 | npm 35 |

36 | 37 |
38 | 39 | 40 | ## Table of Contents 41 | 42 | * [About the Project](#about-the-project) 43 | * [Built With](#built-with) 44 | * [Getting Started](#getting-started) 45 | * [Prerequisites](#prerequisites) 46 | * [Installation](#installation) 47 | * [Usage](#usage) 48 | * [Documentation](#documentation) 49 | * [Roadmap](#roadmap) 50 | * [Contributing](#contributing) 51 | * [License](#license) 52 | * [Contact](#contact) 53 | 54 | 55 | 56 | ## About The Project 57 | 58 | 59 | ### Built With 60 | 61 | * [microbundle](https://github.com/developit/microbundle) 62 | * [webpack](https://webpack.js.org/) 63 | * [cross-fetch](https://github.com/lquixada/cross-fetch) 64 | * [Easybase.io](https://easybase.io) 65 | * [object-observer](https://github.com/gullerya/object-observer) 66 | 67 | 68 | 69 | ## Getting Started 70 | Browser and Node.js compatible library for Easybase **Projects** and **Node Integrations**. This project also serves as the core for [_easybase-react_](https://github.com/easybase/easybase-react). 71 | 72 | ### Prerequisites 73 | 74 | * npm for node projects 75 | * *There are no prerequisites for usage in browser* 76 | 77 | ### Installation 78 | * Node: 79 | ```sh 80 | npm install easybasejs 81 | ``` 82 | * Browser: 83 | ```html 84 | 85 | ... 86 | 87 | ... 88 | 89 | 90 | ``` 91 | 92 | 93 | ## Usage 94 | 95 | The node framework integration uses a query builder, [_EasyQB_](https://easybase.github.io/EasyQB/), to execute CRUD operations. 96 | 97 | The `db()` function will point to your database. Execute queries with `.all` and `.one`. [Read the documentation for `.db` here](https://easybase.github.io/EasyQB/). 98 | 99 | ```javascript 100 | import Easybase from "easybasejs"; 101 | import ebconfig from "./ebconfig"; // Download from Easybase.io 102 | 103 | // Initialize 104 | const table = Easybase.EasybaseProvider({ ebconfig }).db(); 105 | const { e } = table; // Expressions 106 | 107 | // Delete 1 record where 'app name' equals 'MyAppRecord' 108 | await table.delete.where(e.eq('app name', 'MyAppRecord')).one(); 109 | 110 | // Basic select example 'rating' is greater than 15, limited to 10 records. 111 | const records = await table.return().where(e.gt('rating', 15)).limit(10).all(); 112 | console.log(records); 113 | ``` 114 | 115 | A detailed walkthrough of [using serverless database is available here](https://easybase.io/react/). 116 | 117 |
118 | 119 | ### **Cloud Functions** 120 | 121 | The *EasybaseProvider* pattern is not necessary for invoking cloud functions, only *callFunction* is needed. 122 | ```jsx 123 | import { callFunction } from 'easybase-react'; 124 | 125 | function App() { 126 | async function handleButtonClick() { 127 | const response = await callFunction("123456-YOUR-ROUTE", { 128 | hello: "world", 129 | message: "Find me in event.body" 130 | }); 131 | 132 | console.log("Cloud function: " + response); 133 | } 134 | 135 | //... 136 | } 137 | ``` 138 | 139 | Learn more about [deploying cloud functions here](https://easybase.io/react/2021/03/09/The-Easiest-Way-To-Deploy-Cloud-Functions-for-your-React-Projects/). 140 | 141 | 142 | ## Documentation 143 | 144 | Documentation for this library included in the `easybase-react` library [available here](https://easybase.io/docs/easybase-react/). 145 | 146 | 147 | ## Roadmap 148 | 149 | See the [open issues](https://github.com/easybase/easybasejs/issues) for a list of proposed features (and known issues). 150 | 151 | 152 | 153 | ## Contributing 154 | 155 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 156 | 157 | 1. Fork the Project 158 | 2. Create your Feature Branch (`git checkout -b feature/EasybaseFeature`) 159 | 3. Commit your Changes (`git commit -m 'feature'`) 160 | 4. Push to the Branch (`git push origin feature/EasybaseFeature`) 161 | 5. Open a Pull Request 162 | 163 | 164 | ## License 165 | 166 | Distributed under the MIT License. See `LICENSE` for more information. 167 | 168 | 169 | 170 | ## Contact 171 | 172 | Your Name - [@easybase_io](https://twitter.com/easybase_io) - hello@easybase.io 173 | 174 | Project Link: [https://github.com/easybase/easybasejs](https://github.com/easybase/easybasejs) 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /dist/1.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonpEasybase=window.webpackJsonpEasybase||[]).push([[1],{47:function(e,n,r){"use strict";r.r(n),r.d(n,"fromUtf8",(function(){return t})),r.d(n,"toUtf8",(function(){return o}));var t=function(e){return"function"==typeof TextEncoder?function(e){return(new TextEncoder).encode(e)}(e):function(e){for(var n=[],r=0,t=e.length;r>6|192,63&o|128);else if(r+1>18|240,f>>12&63|128,f>>6&63|128,63&f|128)}else n.push(o>>12|224,o>>6&63|128,63&o|128)}return Uint8Array.from(n)}(e)},o=function(e){return"function"==typeof TextDecoder?function(e){return new TextDecoder("utf-8").decode(e)}(e):function(e){for(var n="",r=0,t=e.length;r\n typeof TextEncoder === \"function\" ? textEncoderFromUtf8(input) : jsFromUtf8(input);\n\nexport const toUtf8 = (input: Uint8Array): string =>\n typeof TextDecoder === \"function\" ? textEncoderToUtf8(input) : jsToUtf8(input);\n","/**\n * A declaration of the global TextEncoder and TextDecoder constructors.\n *\n * @see https://encoding.spec.whatwg.org/\n */\n// eslint-disable-next-line @typescript-eslint/no-namespace\nnamespace Encoding {\n interface TextDecoderOptions {\n fatal?: boolean;\n ignoreBOM?: boolean;\n }\n\n interface TextDecodeOptions {\n stream?: boolean;\n }\n\n interface TextDecoder {\n readonly encoding: string;\n readonly fatal: boolean;\n readonly ignoreBOM: boolean;\n decode(input?: ArrayBuffer | ArrayBufferView, options?: TextDecodeOptions): string;\n }\n\n export interface TextDecoderConstructor {\n new (label?: string, options?: TextDecoderOptions): TextDecoder;\n }\n\n interface TextEncoder {\n readonly encoding: \"utf-8\";\n encode(input?: string): Uint8Array;\n }\n\n export interface TextEncoderConstructor {\n new (): TextEncoder;\n }\n}\n\ndeclare const TextDecoder: Encoding.TextDecoderConstructor;\n\ndeclare const TextEncoder: Encoding.TextEncoderConstructor;\n\nexport function fromUtf8(input: string): Uint8Array {\n return new TextEncoder().encode(input);\n}\n\nexport function toUtf8(input: Uint8Array): string {\n return new TextDecoder(\"utf-8\").decode(input);\n}\n","/**\n * Converts a JS string from its native UCS-2/UTF-16 representation into a\n * Uint8Array of the bytes used to represent the equivalent characters in UTF-8.\n *\n * Cribbed from the `goog.crypt.stringToUtf8ByteArray` function in the Google\n * Closure library, though updated to use typed arrays.\n */\nexport const fromUtf8 = (input: string): Uint8Array => {\n const bytes: Array = [];\n for (let i = 0, len = input.length; i < len; i++) {\n const value = input.charCodeAt(i);\n if (value < 0x80) {\n bytes.push(value);\n } else if (value < 0x800) {\n bytes.push((value >> 6) | 0b11000000, (value & 0b111111) | 0b10000000);\n } else if (i + 1 < input.length && (value & 0xfc00) === 0xd800 && (input.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n const surrogatePair = 0x10000 + ((value & 0b1111111111) << 10) + (input.charCodeAt(++i) & 0b1111111111);\n bytes.push(\n (surrogatePair >> 18) | 0b11110000,\n ((surrogatePair >> 12) & 0b111111) | 0b10000000,\n ((surrogatePair >> 6) & 0b111111) | 0b10000000,\n (surrogatePair & 0b111111) | 0b10000000\n );\n } else {\n bytes.push((value >> 12) | 0b11100000, ((value >> 6) & 0b111111) | 0b10000000, (value & 0b111111) | 0b10000000);\n }\n }\n\n return Uint8Array.from(bytes);\n};\n\n/**\n * Converts a typed array of bytes containing UTF-8 data into a native JS\n * string.\n *\n * Partly cribbed from the `goog.crypt.utf8ByteArrayToString` function in the\n * Google Closure library, though updated to use typed arrays and to better\n * handle astral plane code points.\n */\nexport const toUtf8 = (input: Uint8Array): string => {\n let decoded = \"\";\n for (let i = 0, len = input.length; i < len; i++) {\n const byte = input[i];\n if (byte < 0x80) {\n decoded += String.fromCharCode(byte);\n } else if (0b11000000 <= byte && byte < 0b11100000) {\n const nextByte = input[++i];\n decoded += String.fromCharCode(((byte & 0b11111) << 6) | (nextByte & 0b111111));\n } else if (0b11110000 <= byte && byte < 0b101101101) {\n const surrogatePair = [byte, input[++i], input[++i], input[++i]];\n const encoded = \"%\" + surrogatePair.map((byteValue) => byteValue.toString(16)).join(\"%\");\n decoded += decodeURIComponent(encoded);\n } else {\n decoded += String.fromCharCode(\n ((byte & 0b1111) << 12) | ((input[++i] & 0b111111) << 6) | (input[++i] & 0b111111)\n );\n }\n }\n\n return decoded;\n};\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/2.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonpEasybase=window.webpackJsonpEasybase||[]).push([[2],{46:function(t,e,i){var s;!function(e,r){var n={};!function(t){"use strict";t.__esModule=!0,t.digestLength=32,t.blockSize=64;var e=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]);function i(t,i,s,r,n){for(var h,f,a,o,u,d,p,c,g,b,l,y,v;n>=64;){for(h=i[0],f=i[1],a=i[2],o=i[3],u=i[4],d=i[5],p=i[6],c=i[7],b=0;b<16;b++)l=r+4*b,t[b]=(255&s[l])<<24|(255&s[l+1])<<16|(255&s[l+2])<<8|255&s[l+3];for(b=16;b<64;b++)y=((g=t[b-2])>>>17|g<<15)^(g>>>19|g<<13)^g>>>10,v=((g=t[b-15])>>>7|g<<25)^(g>>>18|g<<14)^g>>>3,t[b]=(y+t[b-7]|0)+(v+t[b-16]|0);for(b=0;b<64;b++)y=(((u>>>6|u<<26)^(u>>>11|u<<21)^(u>>>25|u<<7))+(u&d^~u&p)|0)+(c+(e[b]+t[b]|0)|0)|0,v=((h>>>2|h<<30)^(h>>>13|h<<19)^(h>>>22|h<<10))+(h&f^h&a^f&a)|0,c=p,p=d,d=u,u=o+y|0,o=a,a=f,f=h,h=y+v|0;i[0]+=h,i[1]+=f,i[2]+=a,i[3]+=o,i[4]+=u,i[5]+=d,i[6]+=p,i[7]+=c,r+=64,n-=64}return r}var s=function(){function e(){this.digestLength=t.digestLength,this.blockSize=t.blockSize,this.state=new Int32Array(8),this.temp=new Int32Array(64),this.buffer=new Uint8Array(128),this.bufferLength=0,this.bytesHashed=0,this.finished=!1,this.reset()}return e.prototype.reset=function(){return this.state[0]=1779033703,this.state[1]=3144134277,this.state[2]=1013904242,this.state[3]=2773480762,this.state[4]=1359893119,this.state[5]=2600822924,this.state[6]=528734635,this.state[7]=1541459225,this.bufferLength=0,this.bytesHashed=0,this.finished=!1,this},e.prototype.clean=function(){for(var t=0;t0){for(;this.bufferLength<64&&e>0;)this.buffer[this.bufferLength++]=t[s++],e--;64===this.bufferLength&&(i(this.temp,this.state,this.buffer,0,64),this.bufferLength=0)}for(e>=64&&(s=i(this.temp,this.state,t,s,e),e%=64);e>0;)this.buffer[this.bufferLength++]=t[s++],e--;return this},e.prototype.finish=function(t){if(!this.finished){var e=this.bytesHashed,s=this.bufferLength,r=e/536870912|0,n=e<<3,h=e%64<56?64:128;this.buffer[s]=128;for(var f=s+1;f>>24&255,this.buffer[h-7]=r>>>16&255,this.buffer[h-6]=r>>>8&255,this.buffer[h-5]=r>>>0&255,this.buffer[h-4]=n>>>24&255,this.buffer[h-3]=n>>>16&255,this.buffer[h-2]=n>>>8&255,this.buffer[h-1]=n>>>0&255,i(this.temp,this.state,this.buffer,0,h),this.finished=!0}for(f=0;f<8;f++)t[4*f+0]=this.state[f]>>>24&255,t[4*f+1]=this.state[f]>>>16&255,t[4*f+2]=this.state[f]>>>8&255,t[4*f+3]=this.state[f]>>>0&255;return this},e.prototype.digest=function(){var t=new Uint8Array(this.digestLength);return this.finish(t),t},e.prototype._saveState=function(t){for(var e=0;ethis.blockSize)(new s).update(t).finish(e).clean();else for(var i=0;i1&&e.update(t),i&&e.update(i),e.update(s),e.finish(t),s[0]++}t.HMAC=r,t.hash=n,t.default=n,t.hmac=h;var a=new Uint8Array(t.digestLength);t.hkdf=function(t,e,i,s){void 0===e&&(e=a),void 0===s&&(s=32);for(var n=new Uint8Array([1]),o=h(e,t),u=new r(o),d=new Uint8Array(u.digestLength),p=d.length,c=new Uint8Array(s),g=0;g>>24&255,f[1]=p>>>16&255,f[2]=p>>>8&255,f[3]=p>>>0&255,n.reset(),n.update(e),n.update(f),n.finish(o);for(var c=0;c hash\n// sha256.hmac(key, message) -> mac\n// sha256.pbkdf2(password, salt, rounds, dkLen) -> dk\n//\n// Classes:\n//\n// new sha256.Hash()\n// new sha256.HMAC(key)\n//\nexports.digestLength = 32;\nexports.blockSize = 64;\n// SHA-256 constants\nvar K = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,\n 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,\n 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,\n 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,\n 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,\n 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,\n 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,\n 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,\n 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,\n 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,\n 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,\n 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n]);\nfunction hashBlocks(w, v, p, pos, len) {\n var a, b, c, d, e, f, g, h, u, i, j, t1, t2;\n while (len >= 64) {\n a = v[0];\n b = v[1];\n c = v[2];\n d = v[3];\n e = v[4];\n f = v[5];\n g = v[6];\n h = v[7];\n for (i = 0; i < 16; i++) {\n j = pos + i * 4;\n w[i] = (((p[j] & 0xff) << 24) | ((p[j + 1] & 0xff) << 16) |\n ((p[j + 2] & 0xff) << 8) | (p[j + 3] & 0xff));\n }\n for (i = 16; i < 64; i++) {\n u = w[i - 2];\n t1 = (u >>> 17 | u << (32 - 17)) ^ (u >>> 19 | u << (32 - 19)) ^ (u >>> 10);\n u = w[i - 15];\n t2 = (u >>> 7 | u << (32 - 7)) ^ (u >>> 18 | u << (32 - 18)) ^ (u >>> 3);\n w[i] = (t1 + w[i - 7] | 0) + (t2 + w[i - 16] | 0);\n }\n for (i = 0; i < 64; i++) {\n t1 = (((((e >>> 6 | e << (32 - 6)) ^ (e >>> 11 | e << (32 - 11)) ^\n (e >>> 25 | e << (32 - 25))) + ((e & f) ^ (~e & g))) | 0) +\n ((h + ((K[i] + w[i]) | 0)) | 0)) | 0;\n t2 = (((a >>> 2 | a << (32 - 2)) ^ (a >>> 13 | a << (32 - 13)) ^\n (a >>> 22 | a << (32 - 22))) + ((a & b) ^ (a & c) ^ (b & c))) | 0;\n h = g;\n g = f;\n f = e;\n e = (d + t1) | 0;\n d = c;\n c = b;\n b = a;\n a = (t1 + t2) | 0;\n }\n v[0] += a;\n v[1] += b;\n v[2] += c;\n v[3] += d;\n v[4] += e;\n v[5] += f;\n v[6] += g;\n v[7] += h;\n pos += 64;\n len -= 64;\n }\n return pos;\n}\n// Hash implements SHA256 hash algorithm.\nvar Hash = /** @class */ (function () {\n function Hash() {\n this.digestLength = exports.digestLength;\n this.blockSize = exports.blockSize;\n // Note: Int32Array is used instead of Uint32Array for performance reasons.\n this.state = new Int32Array(8); // hash state\n this.temp = new Int32Array(64); // temporary state\n this.buffer = new Uint8Array(128); // buffer for data to hash\n this.bufferLength = 0; // number of bytes in buffer\n this.bytesHashed = 0; // number of total bytes hashed\n this.finished = false; // indicates whether the hash was finalized\n this.reset();\n }\n // Resets hash state making it possible\n // to re-use this instance to hash other data.\n Hash.prototype.reset = function () {\n this.state[0] = 0x6a09e667;\n this.state[1] = 0xbb67ae85;\n this.state[2] = 0x3c6ef372;\n this.state[3] = 0xa54ff53a;\n this.state[4] = 0x510e527f;\n this.state[5] = 0x9b05688c;\n this.state[6] = 0x1f83d9ab;\n this.state[7] = 0x5be0cd19;\n this.bufferLength = 0;\n this.bytesHashed = 0;\n this.finished = false;\n return this;\n };\n // Cleans internal buffers and re-initializes hash state.\n Hash.prototype.clean = function () {\n for (var i = 0; i < this.buffer.length; i++) {\n this.buffer[i] = 0;\n }\n for (var i = 0; i < this.temp.length; i++) {\n this.temp[i] = 0;\n }\n this.reset();\n };\n // Updates hash state with the given data.\n //\n // Optionally, length of the data can be specified to hash\n // fewer bytes than data.length.\n //\n // Throws error when trying to update already finalized hash:\n // instance must be reset to use it again.\n Hash.prototype.update = function (data, dataLength) {\n if (dataLength === void 0) { dataLength = data.length; }\n if (this.finished) {\n throw new Error(\"SHA256: can't update because hash was finished.\");\n }\n var dataPos = 0;\n this.bytesHashed += dataLength;\n if (this.bufferLength > 0) {\n while (this.bufferLength < 64 && dataLength > 0) {\n this.buffer[this.bufferLength++] = data[dataPos++];\n dataLength--;\n }\n if (this.bufferLength === 64) {\n hashBlocks(this.temp, this.state, this.buffer, 0, 64);\n this.bufferLength = 0;\n }\n }\n if (dataLength >= 64) {\n dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength);\n dataLength %= 64;\n }\n while (dataLength > 0) {\n this.buffer[this.bufferLength++] = data[dataPos++];\n dataLength--;\n }\n return this;\n };\n // Finalizes hash state and puts hash into out.\n //\n // If hash was already finalized, puts the same value.\n Hash.prototype.finish = function (out) {\n if (!this.finished) {\n var bytesHashed = this.bytesHashed;\n var left = this.bufferLength;\n var bitLenHi = (bytesHashed / 0x20000000) | 0;\n var bitLenLo = bytesHashed << 3;\n var padLength = (bytesHashed % 64 < 56) ? 64 : 128;\n this.buffer[left] = 0x80;\n for (var i = left + 1; i < padLength - 8; i++) {\n this.buffer[i] = 0;\n }\n this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff;\n this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff;\n this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff;\n this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff;\n this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff;\n this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff;\n this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff;\n this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff;\n hashBlocks(this.temp, this.state, this.buffer, 0, padLength);\n this.finished = true;\n }\n for (var i = 0; i < 8; i++) {\n out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff;\n out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff;\n out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff;\n out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff;\n }\n return this;\n };\n // Returns the final hash digest.\n Hash.prototype.digest = function () {\n var out = new Uint8Array(this.digestLength);\n this.finish(out);\n return out;\n };\n // Internal function for use in HMAC for optimization.\n Hash.prototype._saveState = function (out) {\n for (var i = 0; i < this.state.length; i++) {\n out[i] = this.state[i];\n }\n };\n // Internal function for use in HMAC for optimization.\n Hash.prototype._restoreState = function (from, bytesHashed) {\n for (var i = 0; i < this.state.length; i++) {\n this.state[i] = from[i];\n }\n this.bytesHashed = bytesHashed;\n this.finished = false;\n this.bufferLength = 0;\n };\n return Hash;\n}());\nexports.Hash = Hash;\n// HMAC implements HMAC-SHA256 message authentication algorithm.\nvar HMAC = /** @class */ (function () {\n function HMAC(key) {\n this.inner = new Hash();\n this.outer = new Hash();\n this.blockSize = this.inner.blockSize;\n this.digestLength = this.inner.digestLength;\n var pad = new Uint8Array(this.blockSize);\n if (key.length > this.blockSize) {\n (new Hash()).update(key).finish(pad).clean();\n }\n else {\n for (var i = 0; i < key.length; i++) {\n pad[i] = key[i];\n }\n }\n for (var i = 0; i < pad.length; i++) {\n pad[i] ^= 0x36;\n }\n this.inner.update(pad);\n for (var i = 0; i < pad.length; i++) {\n pad[i] ^= 0x36 ^ 0x5c;\n }\n this.outer.update(pad);\n this.istate = new Uint32Array(8);\n this.ostate = new Uint32Array(8);\n this.inner._saveState(this.istate);\n this.outer._saveState(this.ostate);\n for (var i = 0; i < pad.length; i++) {\n pad[i] = 0;\n }\n }\n // Returns HMAC state to the state initialized with key\n // to make it possible to run HMAC over the other data with the same\n // key without creating a new instance.\n HMAC.prototype.reset = function () {\n this.inner._restoreState(this.istate, this.inner.blockSize);\n this.outer._restoreState(this.ostate, this.outer.blockSize);\n return this;\n };\n // Cleans HMAC state.\n HMAC.prototype.clean = function () {\n for (var i = 0; i < this.istate.length; i++) {\n this.ostate[i] = this.istate[i] = 0;\n }\n this.inner.clean();\n this.outer.clean();\n };\n // Updates state with provided data.\n HMAC.prototype.update = function (data) {\n this.inner.update(data);\n return this;\n };\n // Finalizes HMAC and puts the result in out.\n HMAC.prototype.finish = function (out) {\n if (this.outer.finished) {\n this.outer.finish(out);\n }\n else {\n this.inner.finish(out);\n this.outer.update(out, this.digestLength).finish(out);\n }\n return this;\n };\n // Returns message authentication code.\n HMAC.prototype.digest = function () {\n var out = new Uint8Array(this.digestLength);\n this.finish(out);\n return out;\n };\n return HMAC;\n}());\nexports.HMAC = HMAC;\n// Returns SHA256 hash of data.\nfunction hash(data) {\n var h = (new Hash()).update(data);\n var digest = h.digest();\n h.clean();\n return digest;\n}\nexports.hash = hash;\n// Function hash is both available as module.hash and as default export.\nexports[\"default\"] = hash;\n// Returns HMAC-SHA256 of data under the key.\nfunction hmac(key, data) {\n var h = (new HMAC(key)).update(data);\n var digest = h.digest();\n h.clean();\n return digest;\n}\nexports.hmac = hmac;\n// Fills hkdf buffer like this:\n// T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)\nfunction fillBuffer(buffer, hmac, info, counter) {\n // Counter is a byte value: check if it overflowed.\n var num = counter[0];\n if (num === 0) {\n throw new Error(\"hkdf: cannot expand more\");\n }\n // Prepare HMAC instance for new data with old key.\n hmac.reset();\n // Hash in previous output if it was generated\n // (i.e. counter is greater than 1).\n if (num > 1) {\n hmac.update(buffer);\n }\n // Hash in info if it exists.\n if (info) {\n hmac.update(info);\n }\n // Hash in the counter.\n hmac.update(counter);\n // Output result to buffer and clean HMAC instance.\n hmac.finish(buffer);\n // Increment counter inside typed array, this works properly.\n counter[0]++;\n}\nvar hkdfSalt = new Uint8Array(exports.digestLength); // Filled with zeroes.\nfunction hkdf(key, salt, info, length) {\n if (salt === void 0) { salt = hkdfSalt; }\n if (length === void 0) { length = 32; }\n var counter = new Uint8Array([1]);\n // HKDF-Extract uses salt as HMAC key, and key as data.\n var okm = hmac(salt, key);\n // Initialize HMAC for expanding with extracted key.\n // Ensure no collisions with `hmac` function.\n var hmac_ = new HMAC(okm);\n // Allocate buffer.\n var buffer = new Uint8Array(hmac_.digestLength);\n var bufpos = buffer.length;\n var out = new Uint8Array(length);\n for (var i = 0; i < length; i++) {\n if (bufpos === buffer.length) {\n fillBuffer(buffer, hmac_, info, counter);\n bufpos = 0;\n }\n out[i] = buffer[bufpos++];\n }\n hmac_.clean();\n buffer.fill(0);\n counter.fill(0);\n return out;\n}\nexports.hkdf = hkdf;\n// Derives a key from password and salt using PBKDF2-HMAC-SHA256\n// with the given number of iterations.\n//\n// The number of bytes returned is equal to dkLen.\n//\n// (For better security, avoid dkLen greater than hash length - 32 bytes).\nfunction pbkdf2(password, salt, iterations, dkLen) {\n var prf = new HMAC(password);\n var len = prf.digestLength;\n var ctr = new Uint8Array(4);\n var t = new Uint8Array(len);\n var u = new Uint8Array(len);\n var dk = new Uint8Array(dkLen);\n for (var i = 0; i * len < dkLen; i++) {\n var c = i + 1;\n ctr[0] = (c >>> 24) & 0xff;\n ctr[1] = (c >>> 16) & 0xff;\n ctr[2] = (c >>> 8) & 0xff;\n ctr[3] = (c >>> 0) & 0xff;\n prf.reset();\n prf.update(salt);\n prf.update(ctr);\n prf.finish(u);\n for (var j = 0; j < len; j++) {\n t[j] = u[j];\n }\n for (var j = 2; j <= iterations; j++) {\n prf.reset();\n prf.update(u).finish(u);\n for (var k = 0; k < len; k++) {\n t[k] ^= u[k];\n }\n }\n for (var j = 0; j < len && i * len + j < dkLen; j++) {\n dk[i * len + j] = t[j];\n }\n }\n for (var i = 0; i < len; i++) {\n t[i] = u[i] = 0;\n }\n for (var i = 0; i < 4; i++) {\n ctr[i] = 0;\n }\n prf.clean();\n return dk;\n}\nexports.pbkdf2 = pbkdf2;\n});\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/EasybaseProvider/EasybaseProvider.d.ts: -------------------------------------------------------------------------------- 1 | import { EasybaseProviderProps, ContextValue } from "./types"; 2 | export default function EasybaseProvider({ ebconfig, options }: EasybaseProviderProps): ContextValue; 3 | -------------------------------------------------------------------------------- /dist/EasybaseProvider/auth.d.ts: -------------------------------------------------------------------------------- 1 | import { Globals } from "./types"; 2 | export default function authFactory(globals?: Globals): any; 3 | -------------------------------------------------------------------------------- /dist/EasybaseProvider/db.d.ts: -------------------------------------------------------------------------------- 1 | import { SQW } from "easyqb/types/sq"; 2 | import { NewExpression } from "easyqb/types/expression"; 3 | import { DB_STATUS, Globals, EXECUTE_COUNT, FileFromURI, StatusResponse } from "./types"; 4 | interface IdbFactory { 5 | db: (tableName?: string, userAssociatedRecordsOnly?: boolean) => SQW; 6 | dbEventListener: (callback: (status?: DB_STATUS, queryType?: string, executeCount?: EXECUTE_COUNT, tableName?: string | null, returned?: any) => void) => () => void; 7 | e: NewExpression; 8 | setImage(recordKey: string, columnName: string, image: File | FileFromURI, tableName?: string): Promise; 9 | setVideo(recordKey: string, columnName: string, video: File | FileFromURI, tableName?: string): Promise; 10 | setFile(recordKey: string, columnName: string, file: File | FileFromURI, tableName?: string): Promise; 11 | } 12 | export default function dbFactory(globals?: Globals): IdbFactory; 13 | export {}; 14 | -------------------------------------------------------------------------------- /dist/EasybaseProvider/g.d.ts: -------------------------------------------------------------------------------- 1 | import { Globals, EasybaseProviderProps } from "./types"; 2 | declare const _g: Globals; 3 | export default _g; 4 | export declare function gFactory({ ebconfig, options }: EasybaseProviderProps): Globals; 5 | -------------------------------------------------------------------------------- /dist/EasybaseProvider/object-observer.d.ts: -------------------------------------------------------------------------------- 1 | export class Observable { 2 | static from(target: any, options: any): any; 3 | static isObservable(input: any): boolean; 4 | } 5 | -------------------------------------------------------------------------------- /dist/EasybaseProvider/table.d.ts: -------------------------------------------------------------------------------- 1 | import { Globals } from "./types"; 2 | export default function tableFactory(globals?: Globals): any; 3 | -------------------------------------------------------------------------------- /dist/EasybaseProvider/types.d.ts: -------------------------------------------------------------------------------- 1 | import { SQW } from "easyqb/types/sq"; 2 | import { NewExpression } from "easyqb/types/expression"; 3 | export interface ConfigureFrameOptions { 4 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 5 | offset?: number; 6 | /** Limit the amount of records to be retrieved. Set to -1 or null to return all records. Can be used in combination with offset. */ 7 | limit?: number | null; 8 | /** Table to sync frame with. (Projects only) */ 9 | tableName?: string; 10 | } 11 | export interface GoogleAnalyticsEvents { 12 | login?: boolean; 13 | sign_up?: boolean; 14 | forgot_password?: boolean; 15 | forgot_password_confirm?: boolean; 16 | reset_user_password?: boolean; 17 | get_user_attributes?: boolean; 18 | set_user_attribute?: boolean; 19 | query?: boolean; 20 | full_table_size?: boolean; 21 | table_types?: boolean; 22 | db_one?: boolean; 23 | db_all?: boolean; 24 | } 25 | export interface EasybaseProviderPropsOptions { 26 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 27 | authentication?: string; 28 | /** Log Easybase react status and events to console. */ 29 | logging?: boolean; 30 | /** Google Analytics 4 Measurement ID for activity reporting */ 31 | googleAnalyticsId?: string; 32 | /** **Only Required for React Native** – Google Analytics 4 Measurement Protocol Secret ID for activity reporting. To create a new secret, navigate in the Google Analytics UI to: Admin > Data Streams > choose your stream > Measurement Protocol > Create */ 33 | googleAnalyticsSecret?: string; 34 | /** 35 | * Specify which extra events are tracked in Google Analytics 36 | * 37 | * **default**: 38 | * * Page Mount 39 | * * login 40 | * * sign_up 41 | * * forgot_password 42 | * * forgot_password_confirm 43 | * * reset_user_password 44 | * 45 | */ 46 | googleAnalyticsEventTracking?: GoogleAnalyticsEvents; 47 | } 48 | export interface EasybaseProviderProps { 49 | /** Easybase ebconfig object. Can be downloaded in the integration drawer next to 'React Token'. This is automatically generated. */ 50 | ebconfig: Ebconfig; 51 | /** Optional configuration parameters. */ 52 | options?: EasybaseProviderPropsOptions; 53 | } 54 | export interface FrameConfiguration { 55 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 56 | offset: number; 57 | /** Limit the amount of records to be retrieved. Set to -1 or null to return all records. Can be used in combination with offset. */ 58 | limit: number | null; 59 | /** Table to sync frame with. (Projects only) */ 60 | tableName?: string; 61 | } 62 | export interface Ebconfig { 63 | tt?: string; 64 | integration: string; 65 | version: string; 66 | } 67 | export interface AddRecordOptions { 68 | /** If true, record will be inserted at the end of the collection rather than the front. Overwrites absoluteIndex. */ 69 | insertAtEnd?: boolean; 70 | /** Values to post to Easybase collection. Format is { column name: value } */ 71 | newRecord: Record; 72 | /** Table to post new record to. (Projects only) */ 73 | tableName?: string; 74 | } 75 | export interface DeleteRecordOptions { 76 | record: Record; 77 | /** Table to delete record from. (Projects only) */ 78 | tableName?: string; 79 | } 80 | export interface QueryOptions { 81 | /** Name of the query saved in Easybase's Visual Query Builder */ 82 | queryName: string; 83 | /** If you would like to sort the order of your query by a column. Pass the name of that column here */ 84 | sortBy?: string; 85 | /** By default, columnToSortBy will sort your query by ascending value (1, 2, 3...). To sort by descending set this to true */ 86 | descending?: boolean; 87 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 88 | offset?: number; 89 | /** Limit the amount of records to be retrieved. Can be used in combination with offset. */ 90 | limit?: number; 91 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. Read more: https://easybase.io/about/2020/09/15/Customizing-query-values/ */ 92 | customQuery?: Record; 93 | /** Table to query. (Projects only) */ 94 | tableName?: string; 95 | } 96 | export interface FileFromURI { 97 | /** Path on local device to the attachment. Usually received from react-native-image-picker or react-native-document-picker */ 98 | uri: string; 99 | /** Name of the file with proper extension */ 100 | name: string; 101 | /** File MIME type */ 102 | type: string; 103 | } 104 | export interface UpdateRecordAttachmentOptions { 105 | /** Easybase Record to attach this attachment to */ 106 | record: Record; 107 | /** The name of the column that is of type file/image/video */ 108 | columnName: string; 109 | /** Either an HTML File element containing the correct type of attachment or a FileFromURI object for React Native instances. 110 | * For React Native use libraries such as react-native-image-picker and react-native-document-picker. 111 | * The file name must have a proper file extension corresponding to the attachment. 112 | */ 113 | attachment: File | FileFromURI; 114 | /** Table to post attachment to. (Projects only) */ 115 | tableName?: string; 116 | } 117 | export interface StatusResponse { 118 | /** Returns true if the operation was successful */ 119 | success: boolean; 120 | /** Readable description of the the operation's status */ 121 | message: string; 122 | /** Will represent a corresponding error if an error was thrown during the operation. */ 123 | errorCode?: string; 124 | } 125 | export interface EmailTemplate { 126 | /** Optional header of email that will be sent to user with verification code */ 127 | greeting?: string; 128 | /** Optional name of application for placement within email */ 129 | appName?: string; 130 | /** Optional footer of verification email often used for disclaimers. Can be a valid HTML string */ 131 | footer?: string; 132 | } 133 | export declare enum POST_TYPES { 134 | UPLOAD_ATTACHMENT = "upload_attachment", 135 | HANDSHAKE = "handshake", 136 | VALID_TOKEN = "valid_token", 137 | GET_FRAME = "get_frame", 138 | TABLE_SIZE = "table_size", 139 | COLUMN_TYPES = "column_types", 140 | SYNC_STACK = "sync_stack", 141 | SYNC_DELETE = "sync_delete", 142 | SYNC_INSERT = "sync_insert", 143 | GET_QUERY = "get_query", 144 | USER_ATTRIBUTES = "user_attributes", 145 | SET_ATTRIBUTE = "set_attribute", 146 | SIGN_UP = "sign_up", 147 | REQUEST_TOKEN = "request_token", 148 | EASY_QB = "easyqb", 149 | RESET_PASSWORD = "reset_password", 150 | FORGOT_PASSWORD_SEND = "forgot_password_send", 151 | FORGOT_PASSWORD_CONFIRM = "forgot_password_confirm" 152 | } 153 | export declare enum DB_STATUS { 154 | ERROR = "error", 155 | PENDING = "pending", 156 | SUCCESS = "success" 157 | } 158 | export declare enum EXECUTE_COUNT { 159 | ALL = "all", 160 | ONE = "one" 161 | } 162 | export interface AuthPostResponse { 163 | success: boolean; 164 | data: any; 165 | } 166 | export interface ContextValue { 167 | /** 168 | * Signs out the current user. 169 | */ 170 | signOut(): void; 171 | /** 172 | * Retrieve the currently signed in users attribute object. 173 | * @async 174 | * @return {Promise>} Promise> 175 | */ 176 | getUserAttributes(): Promise>; 177 | /** 178 | * Set a single attribute of the currently signed in user. Can also be updated visually in the Easybase 'Users' tab. 179 | * @async 180 | * @param key Object key. Can be a new key or existing key. 181 | * @param value attribute value. 182 | * @return {Promise} Promise 183 | */ 184 | setUserAttribute(key: string, value: string): Promise; 185 | /** 186 | * Reset the currently signed-in user's password to a new string. 187 | * @async 188 | * @param {string} currentPassword Signed-in user's current password 189 | * @param {string} newPassword New password for user's account 190 | * @return {Promise} Promise 191 | */ 192 | resetUserPassword(currentPassword: string, newPassword: string): Promise; 193 | /** 194 | * Sign in a user that already exists for a project. 195 | * @async 196 | * @param userID unique identifier for new user. Usually an email or phone number. 197 | * @param password user password. 198 | * @return {Promise} Promise 199 | */ 200 | signIn(userID: string, password: string): Promise; 201 | /** 202 | * Create a new user for your project. You must still call signIn() after signing up. 203 | * @async 204 | * @param newUserID unique identifier for new user. Usually an email or phone number. 205 | * @param password user password. Must be at least 8 characters long. 206 | * @param userAttributes Optional object to store user attributes. Can also be edited visually in the Easybase 'Users' tab. 207 | * @return {Promise} Promise 208 | */ 209 | signUp(newUserID: string, password: string, userAttributes?: Record): Promise; 210 | /** 211 | * **DEPRECATED**: Use `db` instead - https://easybase.github.io/EasyQB/ 212 | * @deprecated Use `db` instead - https://easybase.github.io/EasyQB/ 213 | * @param {ConfigureFrameOptions} options ConfigureFrameOptions 214 | * @return {StatusResponse} StatusResponse 215 | */ 216 | configureFrame(options: ConfigureFrameOptions): StatusResponse; 217 | /** 218 | * **DEPRECATED**: Use `db().insert()` instead - https://easybase.github.io/EasyQB/docs/insert_queries.html 219 | * @deprecated Use `db().insert()` instead - https://easybase.github.io/EasyQB/docs/insert_queries.html 220 | * @async 221 | * @param {AddRecordOptions} options AddRecordOptions 222 | * @return {Promise} Promise 223 | */ 224 | addRecord(options: AddRecordOptions): Promise; 225 | /** 226 | * **DEPRECATED**: Use `db().delete()` instead - https://easybase.github.io/EasyQB/docs/delete_queries.html 227 | * @deprecated Use `db().delete()` instead - https://easybase.github.io/EasyQB/docs/delete_queries.html 228 | * @async 229 | * @param {Record} record 230 | * @return {Promise} Promise 231 | */ 232 | deleteRecord(options: DeleteRecordOptions): Promise; 233 | /** 234 | * **DEPRECATED**: Use `db` instead - https://easybase.github.io/EasyQB/ 235 | * @deprecated Use `db` instead - https://easybase.github.io/EasyQB/ 236 | * @async 237 | * @return {Promise} Promise 238 | */ 239 | sync(): Promise; 240 | /** 241 | * **DEPRECATED**: Use the `setImage` function instead. 242 | * @deprecated Use the `setImage` function instead. 243 | * @async 244 | * @param {UpdateRecordAttachmentOptions} options UpdateRecordAttachmentOptions 245 | * @return {Promise} Promise 246 | */ 247 | updateRecordImage(options: UpdateRecordAttachmentOptions): Promise; 248 | /** 249 | * **DEPRECATED**: Use the `setVideo` function instead. 250 | * @deprecated Use the `setVideo` function instead. 251 | * @async 252 | * @param {UpdateRecordAttachmentOptions} options UpdateRecordAttachmentOptions 253 | * @return {Promise} Promise 254 | */ 255 | updateRecordVideo(options: UpdateRecordAttachmentOptions): Promise; 256 | /** 257 | * **DEPRECATED**: Use the `setFile` function instead. 258 | * @deprecated Use the `setFile` function instead. 259 | * @async 260 | * @param {UpdateRecordAttachmentOptions} options UpdateRecordAttachmentOptions 261 | * @return {Promise} Promise 262 | */ 263 | updateRecordFile(options: UpdateRecordAttachmentOptions): Promise; 264 | /** 265 | * Upload an image to your backend and attach it to a specific record. columnName must reference a column of type 'image'. 266 | * The file must have a valid image extension (png, jpg, heic, etc). 267 | * @async 268 | * @param {string} recordKey The '_key' of the record to attach this image to. Can be retrieved like: `db().return("_key").where({ title: "The Lion King" }).one()` 269 | * @param {string} columnName The name of the column that is of type image to attach. 270 | * @param {File | FileFromURI} image Either an HTML File element or a FileFromURI object for React Native instances. For React Native, use libraries such as react-native-image-picker and react-native-document-picker. The file name must have a valid image file extension. 271 | * @param {string} [tableName] Table to post attachment to. (Projects only) 272 | * @return {Promise} Promise 273 | */ 274 | setImage(recordKey: string, columnName: string, image: File | FileFromURI, tableName?: string): Promise; 275 | /** 276 | * Upload a video to your backend and attach it to a specific record. columnName must reference a column of type 'video'. 277 | * The file must have a valid video extension (webm, mp4, mov, etc). 278 | * @async 279 | * @param {string} recordKey The '_key' of the record to attach this image to. Can be retrieved like: `db().return("_key").where({ title: "The Lion King" }).one()` 280 | * @param {string} columnName The name of the column that is of type video to attach. 281 | * @param {File | FileFromURI} video Either an HTML File element or a FileFromURI object for React Native instances. For React Native, use libraries such as react-native-image-picker and react-native-document-picker. The file name must have a valid video file extension. 282 | * @param {string} [tableName] Table to post attachment to. (Projects only) 283 | * @return {Promise} Promise 284 | */ 285 | setVideo(recordKey: string, columnName: string, video: File | FileFromURI, tableName?: string): Promise; 286 | /** 287 | * Upload a file to your backend and attach it to a specific record. columnName must reference a column of type 'file'. 288 | * @async 289 | * @param {string} recordKey The '_key' of the record to attach this image to. Can be retrieved like: `db().return("_key").where({ title: "The Lion King" }).one()` 290 | * @param {string} columnName The name of the column that is of type file to attach. 291 | * @param {File | FileFromURI} file Either an HTML File element or a FileFromURI object for React Native instances. For React Native, use libraries such as react-native-image-picker and react-native-document-picker. 292 | * @param {string} [tableName] Table to post attachment to. (Projects only) 293 | * @return {Promise} Promise 294 | */ 295 | setFile(recordKey: string, columnName: string, file: File | FileFromURI, tableName?: string): Promise; 296 | /** 297 | * **DEPRECATED**: Use `db` instead - https://easybase.github.io/EasyQB/ 298 | * @deprecated Use `db` instead - https://easybase.github.io/EasyQB/ 299 | * @return {Record[]} Array of records corresponding to the current frame. Call sync() to push changes that you have made to this array. 300 | */ 301 | Frame(): Record[]; 302 | /** 303 | * **DEPRECATED**: Use `db` instead - https://easybase.github.io/EasyQB/ 304 | * @deprecated Use `db` instead - https://easybase.github.io/EasyQB/ 305 | * @param {number} [index] Passing an index will only return the object at that index in your Frame, rather than the entire array. This is useful for editing single objects based on an index. 306 | * @return {Record} Single record corresponding to that object within the current frame. Call sync() to push changes that you have made to this object. 307 | */ 308 | Frame(index: number): Record; 309 | /** 310 | * Gets the number of records in your table. 311 | * @async 312 | * @returns {Promise} The the number of records in your table. 313 | */ 314 | fullTableSize(): Promise; 315 | /** 316 | * Gets the number of records in your table. 317 | * @async 318 | * @param {string} [tableName] Name of table to get the sizes of. (Projects only) 319 | * @returns {Promise} The the number of records in your table. 320 | */ 321 | fullTableSize(tableName: string): Promise; 322 | /** 323 | * Retrieve an object detailing the columns in your table mapped to their corresponding type. 324 | * @async 325 | * @returns {Promise>} Object detailing the columns in your table mapped to their corresponding type. 326 | */ 327 | tableTypes(): Promise>; 328 | /** 329 | * Retrieve an object detailing the columns in your table mapped to their corresponding type. 330 | * @async 331 | * @param {string} [tableName] Name of table to get the types of. (Projects only) 332 | * @returns {Promise>} Object detailing the columns in your table mapped to their corresponding type. 333 | */ 334 | tableTypes(tableName: string): Promise>; 335 | /** 336 | * View your frames current configuration 337 | * @returns {Record} Object contains the `offset` and `length` of your current frame. 338 | */ 339 | currentConfiguration(): FrameConfiguration; 340 | /** 341 | * @async 342 | * View a query by name. This returns an isolated array that has no effect on your frame or frame configuration. sync() and Frame() have no 343 | * relationship with a Query(). An edited Query cannot be synced with your database, use Frame() for realtime 344 | * database array features. 345 | * @param {QueryOptions} options QueryOptions 346 | * @return {Promise[]>} Isolated array of records in the same form as Frame(). Editing this array has no effect and cannot be synced with your database. Use Frame() for realtime database features. 347 | */ 348 | Query(options: QueryOptions): Promise[]>; 349 | /** 350 | * Instantiate EasyQB instance for dynamic CRUD query building: https://easybase.github.io/EasyQB/ 351 | * @param {string} [tableName] Name of your table. 352 | * @param {boolean} [userAssociatedRecordsOnly] **PROJECTS ONLY** Operations will only be performed on records already associated to the currently signed-in user. Inserted records will automatically be associate to the user. 353 | * @returns {SQW} EasyQB object for dynamic querying: https://easybase.github.io/EasyQB/ 354 | */ 355 | db(tableName?: string, userAssociatedRecordsOnly?: boolean): SQW; 356 | /** 357 | * Subscribe to db events, invoked by calling `.all` or `.one`: https://easybase.github.io/EasyQB/ 358 | * @param {function(status?: DB_STATUS, queryType?: string, executeCount?: EXECUTE_COUNT, tableName?: string | null, returned?: any):void} [callback] Callback function to execute on db operations. 359 | * @returns {function():void} Calling this function unsubscribes your callback function from events. 360 | */ 361 | dbEventListener(callback: (status?: DB_STATUS, queryType?: string, executeCount?: EXECUTE_COUNT, tableName?: string | null, returned?: any) => void): () => void; 362 | /** 363 | * Expressions and operations builder for `.db()`, used to create complex conditions, aggregators, and clauses. https://easybase.github.io/EasyQB/docs/operations.html 364 | */ 365 | e: NewExpression; 366 | /** 367 | * @async 368 | * Trigger an email to the given username with a verification code to reset the user's password. This verification 369 | * code is used in the `forgotPasswordConfirm` function, along with a new password. **The username must be the user's email address**. 370 | * @param {string} username A username which must also be a valid email address 371 | * @param {EmailTemplate} emailTemplate Optional details for the formatting & content of the verification email 372 | * @return {Promise} A StatusResponse corresponding to the successful sending of a verification code email 373 | */ 374 | forgotPassword(username: string, emailTemplate?: EmailTemplate): Promise; 375 | /** 376 | * @async 377 | * Confirm the resetting of an unauthenticated users password. This function is invoked after `forgotPassword` is used to trigger 378 | * an email containing a verification code to the given username [*which must also be an email*]. The user's randomly generated 379 | * verification code from their email is passed in the first parameter. 380 | * @param {string} code Verification code found in the email sent from the `forgotPassword` function 381 | * @param {string} username The same username (email) used in the `forgotPassword` function 382 | * @param {string} newPassword The new password for the corresponding verified user 383 | * @return {Promise} A StatusResponse corresponding to the successful setting of a new password 384 | */ 385 | forgotPasswordConfirm(code: string, username: string, newPassword: string): Promise; 386 | /** 387 | * Retrieve the currently signed-in user's ID. 388 | * @return {string | undefined} The currently signed-in user's ID (username), otherwise undefined. 389 | */ 390 | userID(): string | undefined; 391 | } 392 | export interface Globals { 393 | ebconfig: Ebconfig; 394 | token: string; 395 | refreshToken: string; 396 | session: number; 397 | options: EasybaseProviderPropsOptions; 398 | instance: "Node" | "React" | "React Native"; 399 | mounted: boolean; 400 | newTokenCallback(): void; 401 | userID: string | undefined; 402 | analyticsEnabled: boolean; 403 | analyticsEventsToTrack: GoogleAnalyticsEvents; 404 | analyticsEvent(eventTitle: string, params?: Record): void; 405 | analyticsIdentify(hashedUserId: string): void; 406 | GA_USER_ID_SALT: string; 407 | } 408 | -------------------------------------------------------------------------------- /dist/EasybaseProvider/utils.d.ts: -------------------------------------------------------------------------------- 1 | import { Globals } from "./types"; 2 | export default function utilsFactory(globals?: Globals): any; 3 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as EasybaseProvider } from "./EasybaseProvider/EasybaseProvider"; 2 | interface GetOptions { 3 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 4 | integrationID: string; 5 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 6 | offset?: number; 7 | /** Limit the amount of records to be retrieved. Can be used in combination with offset. */ 8 | limit?: number; 9 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 10 | authentication?: string; 11 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 12 | customQuery?: Record; 13 | } 14 | /** 15 | * 16 | * @param {GetOptions} options GetOptions. 17 | * @returns {Promise} Array of records. 18 | * 19 | */ 20 | export declare function get(options: GetOptions): Promise>>; 21 | interface PostOptions { 22 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 23 | integrationID: string; 24 | /** Values to post to Easybase collection. Format is { column name: value } */ 25 | newRecord: Record; 26 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 27 | authentication?: string; 28 | /** If true, record will be inserted at the end of the collection rather than the front. */ 29 | insertAtEnd?: boolean; 30 | } 31 | /** 32 | * 33 | * @param {PostOptions} options PostOptions 34 | * @returns {Promise} Post status. 35 | * 36 | */ 37 | export declare function post(options: PostOptions): Promise; 38 | interface UpdateOptions { 39 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 40 | integrationID: string; 41 | /** Values to update records with. Format is { column_name: new value } */ 42 | updateValues: Record; 43 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 44 | authentication?: string; 45 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 46 | customQuery?: Record; 47 | } 48 | /** 49 | * 50 | * @param {UpdateOptions} options UpdateOptions 51 | * @returns {Promise} Update status. 52 | */ 53 | export declare function update(options: UpdateOptions): Promise; 54 | interface DeleteOptions { 55 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 56 | integrationID: string; 57 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 58 | authentication?: string; 59 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 60 | customQuery?: Record; 61 | } 62 | /** 63 | * 64 | * @param {DeleteOptions} options DeleteOptions 65 | * @return {Promise} Delete status. 66 | */ 67 | export declare function Delete(options: DeleteOptions): Promise; 68 | /** 69 | * @async 70 | * Call a cloud function, created in Easybase interface. 71 | * @param {string} route Route as detailed in Easybase. Found under 'Deploy'. Will be in the form of ####...####-function-name. 72 | * @param {Record} postBody Optional object to pass as the body of the POST request. This object will available in your cloud function's event.body. 73 | * @return {Promise} Response from your cloud function. Detailed with a call to 'return context.succeed("RESPONSE")'. 74 | */ 75 | export declare function callFunction(route: string, postBody?: Record): Promise; 76 | -------------------------------------------------------------------------------- /dist/restTypes.d.ts: -------------------------------------------------------------------------------- 1 | export interface GetOptions { 2 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 3 | integrationID: string; 4 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 5 | offset?: number; 6 | /** Limit the amount of records to be retrieved. Can be used in combination with offset. */ 7 | limit?: number; 8 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 9 | authentication?: string; 10 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 11 | customQuery?: Record; 12 | } 13 | export interface PostOptions { 14 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 15 | integrationID: string; 16 | /** Values to post to Easybase collection. Format is { column name: value } */ 17 | newRecord: Record; 18 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 19 | authentication?: string; 20 | /** If true, record will be inserted at the end of the collection rather than the front. */ 21 | insertAtEnd?: boolean; 22 | } 23 | export interface UpdateOptions { 24 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 25 | integrationID: string; 26 | /** Values to update records with. Format is { column_name: new value } */ 27 | updateValues: Record; 28 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 29 | authentication?: string; 30 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 31 | customQuery?: Record; 32 | } 33 | export interface DeleteOptions { 34 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 35 | integrationID: string; 36 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 37 | authentication?: string; 38 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 39 | customQuery?: Record; 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easybasejs", 3 | "version": "4.2.22", 4 | "description": "Browser and Node.js package for use with Easybase.io", 5 | "license": "MIT", 6 | "source": "src/index.ts", 7 | "main": "dist/index.js", 8 | "module": "dist/index.modern.js", 9 | "scripts": { 10 | "type-check": "tsc --noEmit", 11 | "type-check:watch": "npm run type-check -- --watch", 12 | "build": "npm run build:types && npm run build:js && npx webpack", 13 | "build:types": "tsc --emitDeclarationOnly", 14 | "build:js": "microbundle --no-compress --format modern,cjs", 15 | "test": "echo 'No test specified' || true" 16 | }, 17 | "files": [ 18 | "dist", 19 | "src" 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/easybase/easybasejs.git" 24 | }, 25 | "author": "Easybase", 26 | "bugs": { 27 | "url": "https://github.com/easybase/easybasejs/issues" 28 | }, 29 | "homepage": "https://github.com/easybase/easybasejs#readme", 30 | "devDependencies": { 31 | "@typescript-eslint/eslint-plugin": "^4.5.0", 32 | "@typescript-eslint/parser": "^4.5.0", 33 | "eslint": "^7.4.0", 34 | "microbundle": "^0.12.4", 35 | "source-map-loader": "^1.1.1", 36 | "ts-loader": "^8.0.7", 37 | "typescript": "^4.0.3", 38 | "webpack": "^4.43.0", 39 | "webpack-cli": "^3.3.12" 40 | }, 41 | "dependencies": { 42 | "@aws-sdk/util-utf8-browser": "^3.18.0", 43 | "@glidejs/glide": "^3.4.1", 44 | "cross-fetch": "^3.1.4", 45 | "easyqb": "^1.0.20", 46 | "fast-deep-equal": "^3.1.3", 47 | "fast-sha256": "^1.3.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/EasybaseProvider/EasybaseProvider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EasybaseProviderProps, 3 | FrameConfiguration, 4 | POST_TYPES, 5 | FileFromURI, 6 | ContextValue, 7 | AddRecordOptions, 8 | StatusResponse, 9 | ConfigureFrameOptions, 10 | UpdateRecordAttachmentOptions, 11 | DeleteRecordOptions 12 | } from "./types"; 13 | import { gFactory } from "./g"; 14 | import deepEqual from "fast-deep-equal"; 15 | import { Observable } from "./object-observer"; 16 | import imageExtensions from "./assets/image-extensions.json"; 17 | import videoExtensions from "./assets/video-extensions.json"; 18 | import authFactory from "./auth"; 19 | import utilsFactory from "./utils"; 20 | import tableFactory from "./table"; 21 | import dbFactory from './db'; 22 | 23 | export default function EasybaseProvider({ ebconfig, options }: EasybaseProviderProps): ContextValue { 24 | if (typeof ebconfig !== 'object' || ebconfig === null || ebconfig === undefined) { 25 | console.error("No ebconfig object passed. do `import ebconfig from \"./ebconfig.js\"` and pass it to the Easybase provider"); 26 | return false as any; 27 | } else if (!ebconfig.integration) { 28 | console.error("Invalid ebconfig object passed. Download ebconfig.js from Easybase.io and try again."); 29 | return false as any; 30 | } 31 | 32 | const g = gFactory({ ebconfig, options }); 33 | 34 | const { 35 | tokenPost, 36 | tokenPostAttachment, 37 | signUp, 38 | setUserAttribute, 39 | getUserAttributes, 40 | signIn, 41 | signOut, 42 | resetUserPassword, 43 | forgotPassword, 44 | forgotPasswordConfirm, 45 | userID 46 | } = authFactory(g); 47 | 48 | const { 49 | Query, 50 | fullTableSize, 51 | tableTypes 52 | } = tableFactory(g); 53 | 54 | const { 55 | db, 56 | dbEventListener, 57 | e, 58 | setFile, 59 | setImage, 60 | setVideo 61 | } = dbFactory(g); 62 | 63 | const { log } = utilsFactory(g); 64 | 65 | // eslint-disable-next-line dot-notation 66 | const isIE = typeof document !== 'undefined' && !!document['documentMode']; 67 | 68 | if (isIE) { 69 | console.error("EASYBASE — easybasejs does not support Internet Explorer. Please use a different browser."); 70 | } 71 | 72 | if (g.ebconfig.tt && g.ebconfig.integration.split("-")[0].toUpperCase() !== "PROJECT") { 73 | g.mounted = false; 74 | } else { 75 | g.mounted = true; 76 | } 77 | 78 | g.instance = "Node"; 79 | 80 | let _isFrameInitialized = true; 81 | let _frameConfiguration: FrameConfiguration = { 82 | offset: 0, 83 | limit: 0 84 | }; 85 | const _observedChangeStack: Record[] = []; 86 | let _recordIdMap: WeakMap, "string"> = new WeakMap(); 87 | let _observableFrame = { 88 | observe: (_) => { }, 89 | unobserve: () => { } 90 | } 91 | let _frame: Record[] = []; 92 | 93 | let isSyncing = false; 94 | 95 | function Frame(): Record[]; 96 | function Frame(index: number): Record; 97 | function Frame(index?: number): Record[] | Record { 98 | if (typeof index === "number") { 99 | return _observableFrame[index]; 100 | } else { 101 | return _observableFrame; 102 | } 103 | } 104 | 105 | const _recordIDExists = (record: Record): Boolean => !!_recordIdMap.get(record); 106 | 107 | const configureFrame = (options: ConfigureFrameOptions): StatusResponse => { 108 | _frameConfiguration = { ..._frameConfiguration }; 109 | 110 | if (options.limit !== undefined) _frameConfiguration.limit = options.limit; 111 | if (options.offset !== undefined && options.offset >= 0) _frameConfiguration.offset = options.offset; 112 | if (options.tableName !== undefined) _frameConfiguration.tableName = options.tableName; 113 | 114 | _isFrameInitialized = false; 115 | return { 116 | message: "Successfully configured frame. Run sync() for changes to be shown in frame", 117 | success: true 118 | } 119 | } 120 | 121 | const currentConfiguration = (): FrameConfiguration => ({ ..._frameConfiguration }); 122 | 123 | const deleteRecord = async (options: DeleteRecordOptions): Promise => { 124 | const _frameRecord = _frame.find(ele => deepEqual(ele, options.record)); 125 | 126 | if (_frameRecord && _recordIdMap.get(_frameRecord)) { 127 | const res = await tokenPost(POST_TYPES.SYNC_DELETE, { 128 | _id: _recordIdMap.get(_frameRecord), 129 | tableName: options.tableName 130 | }); 131 | return { 132 | success: res.success, 133 | message: res.data 134 | } 135 | } else { 136 | try { 137 | const res = await tokenPost(POST_TYPES.SYNC_DELETE, { 138 | record: options.record, 139 | tableName: options.tableName 140 | }); 141 | return { 142 | success: res.success, 143 | message: res.data 144 | } 145 | } catch (error) { 146 | console.error("Easybase Error: deleteRecord failed ", error); 147 | return { 148 | success: false, 149 | message: "Easybase Error: deleteRecord failed " + error, 150 | errorCode: error.errorCode || undefined 151 | } 152 | } 153 | } 154 | } 155 | 156 | const addRecord = async (options: AddRecordOptions): Promise => { 157 | const defaultValues: AddRecordOptions = { 158 | insertAtEnd: false, 159 | newRecord: {}, 160 | tableName: undefined 161 | } 162 | 163 | const fullOptions: AddRecordOptions = { ...defaultValues, ...options }; 164 | 165 | try { 166 | const res = await tokenPost(POST_TYPES.SYNC_INSERT, fullOptions); 167 | return { 168 | message: res.data, 169 | success: res.success 170 | } 171 | } catch (error) { 172 | console.error("Easybase Error: addRecord failed ", error); 173 | return { 174 | message: "Easybase Error: addRecord failed " + error, 175 | success: false, 176 | errorCode: error.errorCode || undefined 177 | } 178 | } 179 | } 180 | 181 | // Only allow the deletion of one element at a time 182 | // First handle shifting of the array size. Then iterate 183 | const sync = async (): Promise => { 184 | const _realignFrames = (newData: Record[]) => { 185 | let isNewDataTheSame = true; 186 | 187 | if (newData.length !== _frame.length) { 188 | isNewDataTheSame = false; 189 | } else { 190 | for (let i = 0; i < newData.length; i++) { 191 | const newDataNoId = { ...newData[i] }; 192 | delete newDataNoId._id; 193 | if (!deepEqual(newDataNoId, _frame[i])) { 194 | isNewDataTheSame = false; 195 | break; 196 | } 197 | } 198 | } 199 | 200 | if (!isNewDataTheSame) { 201 | const oldframe = [..._frame]; 202 | oldframe.length = newData.length; 203 | _recordIdMap = new WeakMap(); 204 | for (let i = 0; i < newData.length; i++) { 205 | const currNewEle = newData[i]; 206 | _recordIdMap.set(currNewEle, currNewEle._id); 207 | delete currNewEle._id; 208 | oldframe[i] = currNewEle; 209 | } 210 | _frame = oldframe; 211 | _observableFrame.unobserve(); 212 | _observableFrame = Observable.from(_frame); 213 | 214 | _observableFrame.observe((allChanges: any[]) => { 215 | allChanges.forEach((change: any) => { 216 | _observedChangeStack.push({ 217 | type: change.type, 218 | path: change.path, 219 | value: change.value, 220 | _id: _recordIdMap.get(_frame[Number(change.path[0])]) 221 | // Not bringing change.object or change.oldValue 222 | }); 223 | log(JSON.stringify({ 224 | type: change.type, 225 | path: change.path, 226 | value: change.value, 227 | _id: _recordIdMap.get(_frame[Number(change.path[0])]) 228 | // Not bringing change.object or change.oldValue 229 | })) 230 | }); 231 | }); 232 | } 233 | } 234 | 235 | if (isSyncing) { 236 | return { 237 | success: false, 238 | message: "Easybase Error: the provider is currently syncing, use 'await sync()' before calling sync() again" 239 | }; 240 | } 241 | 242 | isSyncing = true; 243 | 244 | if (_isFrameInitialized) { 245 | if (_observedChangeStack.length > 0) { 246 | log("Stack change: ", _observedChangeStack); 247 | const res = await tokenPost(POST_TYPES.SYNC_STACK, { 248 | stack: _observedChangeStack, 249 | ..._frameConfiguration 250 | }); 251 | if (res.success) { 252 | _observedChangeStack.length = 0; 253 | } 254 | } 255 | } 256 | 257 | try { 258 | const res = await tokenPost(POST_TYPES.GET_FRAME, _frameConfiguration); 259 | 260 | // Check if the array recieved from db is the same as frame 261 | // If not, update it and send useFrameEffect 262 | 263 | if (res.success === false) { 264 | console.error(res.data); 265 | isSyncing = false; 266 | return { 267 | success: false, 268 | message: "" + res.data 269 | } 270 | } else { 271 | _isFrameInitialized = true; 272 | _realignFrames(res.data); 273 | isSyncing = false; 274 | return { 275 | message: 'Success. Call frame for data', 276 | success: true 277 | } 278 | } 279 | } catch (error) { 280 | console.error("Easybase Error: get failed ", error); 281 | isSyncing = false; 282 | return { 283 | success: false, 284 | message: "Easybase Error: get failed " + error, 285 | errorCode: error.errorCode || undefined 286 | } 287 | } 288 | } 289 | 290 | const updateRecordImage = async (options: UpdateRecordAttachmentOptions): Promise => { 291 | const res = await _updateRecordAttachment(options, "image"); 292 | return res; 293 | } 294 | const updateRecordVideo = async (options: UpdateRecordAttachmentOptions): Promise => { 295 | const res = await _updateRecordAttachment(options, "video"); 296 | return res; 297 | } 298 | const updateRecordFile = async (options: UpdateRecordAttachmentOptions): Promise => { 299 | const res = await _updateRecordAttachment(options, "file"); 300 | return res; 301 | } 302 | 303 | const _updateRecordAttachment = async (options: UpdateRecordAttachmentOptions, type: string): Promise => { 304 | const _frameRecord: Record | undefined = _frame.find(ele => deepEqual(ele, options.record)); 305 | 306 | if (_frameRecord === undefined || !_recordIDExists(_frameRecord)) { 307 | log("Attempting to add attachment to a new record that has not been synced. Please sync() before trying to add attachment."); 308 | return { 309 | success: false, 310 | message: "Attempting to add attachment to a new record that has not been synced. Please sync() before trying to add attachment." 311 | } 312 | } 313 | 314 | const ext: string = options.attachment.name.split(".").pop()!.toLowerCase(); 315 | 316 | log(ext); 317 | 318 | if (type === "image" && !imageExtensions.includes(ext)) { 319 | return { 320 | success: false, 321 | message: "Image files must have a proper image extension in the file name" 322 | }; 323 | } 324 | 325 | if (type === "video" && !videoExtensions.includes(ext)) { 326 | return { 327 | success: false, 328 | message: "Video files must have a proper video extension in the file name" 329 | }; 330 | } 331 | 332 | function isFileFromURI(f: File | FileFromURI): f is FileFromURI { 333 | return (f as FileFromURI).uri !== undefined; 334 | } 335 | 336 | const formData = new FormData(); 337 | 338 | if (isFileFromURI(options.attachment)) { 339 | formData.append("file", options.attachment as any); 340 | formData.append("name", options.attachment.name); 341 | } else { 342 | formData.append("file", options.attachment); 343 | formData.append("name", options.attachment.name); 344 | } 345 | 346 | const customHeaders = { 347 | 'Eb-upload-type': type, 348 | 'Eb-column-name': options.columnName, 349 | 'Eb-record-id': _recordIdMap.get(_frameRecord), 350 | 'Eb-table-name': options.tableName 351 | } 352 | 353 | const res = await tokenPostAttachment(formData, customHeaders); 354 | 355 | await sync(); 356 | 357 | return { 358 | message: res.data, 359 | success: res.success 360 | }; 361 | } 362 | 363 | const c: ContextValue = { 364 | /** +++ Will be deprecated */ 365 | configureFrame, 366 | addRecord, 367 | deleteRecord, 368 | sync, 369 | Frame, 370 | currentConfiguration, 371 | /** --- */ 372 | updateRecordImage, 373 | updateRecordVideo, 374 | updateRecordFile, 375 | fullTableSize, 376 | tableTypes, 377 | Query, 378 | signIn, 379 | signOut, 380 | signUp, 381 | resetUserPassword, 382 | setUserAttribute, 383 | getUserAttributes, 384 | db, 385 | dbEventListener, 386 | e, 387 | setFile, 388 | setImage, 389 | setVideo, 390 | forgotPassword, 391 | forgotPasswordConfirm, 392 | userID 393 | } 394 | 395 | return c; 396 | } 397 | -------------------------------------------------------------------------------- /src/EasybaseProvider/assets/image-extensions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "ase", 3 | "art", 4 | "bmp", 5 | "blp", 6 | "cd5", 7 | "cit", 8 | "cpt", 9 | "cr2", 10 | "cut", 11 | "dds", 12 | "dib", 13 | "djvu", 14 | "egt", 15 | "exif", 16 | "gif", 17 | "gpl", 18 | "grf", 19 | "icns", 20 | "heic", 21 | "ico", 22 | "iff", 23 | "jng", 24 | "jpeg", 25 | "jpg", 26 | "jfif", 27 | "jp2", 28 | "jps", 29 | "lbm", 30 | "max", 31 | "miff", 32 | "mng", 33 | "msp", 34 | "nitf", 35 | "ota", 36 | "pbm", 37 | "pc1", 38 | "pc2", 39 | "pc3", 40 | "pcf", 41 | "pcx", 42 | "pdn", 43 | "pgm", 44 | "PI1", 45 | "PI2", 46 | "PI3", 47 | "pict", 48 | "pct", 49 | "pnm", 50 | "pns", 51 | "ppm", 52 | "psb", 53 | "psd", 54 | "pdd", 55 | "psp", 56 | "px", 57 | "pxm", 58 | "pxr", 59 | "qfx", 60 | "raw", 61 | "rle", 62 | "sct", 63 | "sgi", 64 | "rgb", 65 | "int", 66 | "bw", 67 | "tga", 68 | "tiff", 69 | "tif", 70 | "vtf", 71 | "xbm", 72 | "xcf", 73 | "xpm", 74 | "3dv", 75 | "amf", 76 | "ai", 77 | "awg", 78 | "cgm", 79 | "cdr", 80 | "cmx", 81 | "dxf", 82 | "e2d", 83 | "egt", 84 | "eps", 85 | "fs", 86 | "gbr", 87 | "odg", 88 | "svg", 89 | "stl", 90 | "vrml", 91 | "x3d", 92 | "sxd", 93 | "v2d", 94 | "vnd", 95 | "wmf", 96 | "emf", 97 | "art", 98 | "xar", 99 | "png", 100 | "webp", 101 | "jxr", 102 | "hdp", 103 | "wdp", 104 | "cur", 105 | "ecw", 106 | "iff", 107 | "lbm", 108 | "liff", 109 | "nrrd", 110 | "pam", 111 | "pcx", 112 | "pgf", 113 | "sgi", 114 | "rgb", 115 | "rgba", 116 | "bw", 117 | "int", 118 | "inta", 119 | "sid", 120 | "ras", 121 | "sun", 122 | "tga" 123 | ] 124 | -------------------------------------------------------------------------------- /src/EasybaseProvider/assets/video-extensions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "3g2", 3 | "3gp", 4 | "aaf", 5 | "asf", 6 | "avchd", 7 | "avi", 8 | "drc", 9 | "flv", 10 | "m2v", 11 | "m4p", 12 | "m4v", 13 | "mkv", 14 | "mng", 15 | "mov", 16 | "mp2", 17 | "mp4", 18 | "mpe", 19 | "mpeg", 20 | "mpg", 21 | "mpv", 22 | "mxf", 23 | "nsv", 24 | "ogg", 25 | "ogv", 26 | "qt", 27 | "rm", 28 | "rmvb", 29 | "roq", 30 | "svi", 31 | "vob", 32 | "webm", 33 | "wmv", 34 | "yuv" 35 | ] 36 | -------------------------------------------------------------------------------- /src/EasybaseProvider/auth.ts: -------------------------------------------------------------------------------- 1 | import { POST_TYPES, AuthPostResponse, Globals, StatusResponse, EmailTemplate } from "./types"; 2 | import _g from "./g"; 3 | import utilsFactory from "./utils"; 4 | import fetch from 'cross-fetch'; 5 | 6 | export default function authFactory(globals?: Globals): any { 7 | const g = globals || _g; 8 | 9 | const { generateBareUrl, generateAuthBody, log } = utilsFactory(g); 10 | 11 | function _clearTokens() { 12 | g.token = ""; 13 | g.refreshToken = ""; 14 | g.newTokenCallback(); 15 | g.userID = undefined; 16 | } 17 | 18 | const getUserAttributes = async (): Promise> => { 19 | try { 20 | const attrsRes = await tokenPost(POST_TYPES.USER_ATTRIBUTES); 21 | g.analyticsEnabled && g.analyticsEventsToTrack.get_user_attributes && g.analyticsEvent('get_user_attributes'); 22 | return attrsRes.data; 23 | } catch (error) { 24 | log(error) 25 | return error; 26 | } 27 | } 28 | 29 | const setUserAttribute = async (key: string, value: string): Promise => { 30 | try { 31 | const setAttrsRes = await tokenPost(POST_TYPES.SET_ATTRIBUTE, { 32 | key, 33 | value 34 | }); 35 | g.analyticsEnabled && g.analyticsEventsToTrack.set_user_attribute && g.analyticsEvent('set_user_attribute', { key }); 36 | return { 37 | success: setAttrsRes.success, 38 | message: JSON.stringify(setAttrsRes.data) 39 | }; 40 | } catch (error) { 41 | return { 42 | success: false, 43 | message: error.message || "Error", 44 | errorCode: error.errorCode || undefined 45 | }; 46 | } 47 | } 48 | 49 | const forgotPassword = async (username: string, emailTemplate?: EmailTemplate): Promise => { 50 | try { 51 | const setAttrsRes = await tokenPost(POST_TYPES.FORGOT_PASSWORD_SEND, { 52 | username, 53 | emailTemplate 54 | }); 55 | g.analyticsEnabled && g.analyticsEventsToTrack.forgot_password && g.analyticsEvent('forgot_password'); 56 | return { 57 | success: setAttrsRes.success, 58 | message: setAttrsRes.data 59 | }; 60 | } catch (error) { 61 | return { 62 | success: false, 63 | message: error.message || "Error", 64 | errorCode: error.errorCode || undefined 65 | }; 66 | } 67 | } 68 | 69 | const forgotPasswordConfirm = async (code: string, username: string, newPassword: string): Promise => { 70 | try { 71 | const setAttrsRes = await tokenPost(POST_TYPES.FORGOT_PASSWORD_CONFIRM, { 72 | username, 73 | code, 74 | newPassword 75 | }); 76 | g.analyticsEnabled && g.analyticsEventsToTrack.forgot_password_confirm && g.analyticsEvent('forgot_password_confirm'); 77 | return { 78 | success: setAttrsRes.success, 79 | message: setAttrsRes.data 80 | }; 81 | } catch (error) { 82 | return { 83 | success: false, 84 | message: error.message || "Error", 85 | errorCode: error.errorCode || undefined 86 | }; 87 | } 88 | } 89 | 90 | const signUp = async (newUserID: string, password: string, userAttributes?: Record): Promise => { 91 | try { 92 | const signUpRes = await tokenPost(POST_TYPES.SIGN_UP, { 93 | newUserID, 94 | password, 95 | userAttributes 96 | }); 97 | g.analyticsEnabled && g.analyticsEventsToTrack.sign_up && g.analyticsEvent('sign_up', { method: "Easybase" }); 98 | return { 99 | success: signUpRes.success, 100 | message: signUpRes.data 101 | } 102 | } catch (error) { 103 | return { 104 | success: false, 105 | message: error.message || "Error", 106 | errorCode: error.errorCode || undefined 107 | } 108 | } 109 | } 110 | 111 | const signIn = async (userID: string, password: string): Promise => { 112 | const t1 = Date.now(); 113 | g.session = Math.floor(100000000 + Math.random() * 900000000); 114 | 115 | const integrationType = g.ebconfig.integration.split("-")[0].toUpperCase() === "PROJECT" ? "PROJECT" : "REACT"; 116 | 117 | try { 118 | const res = await fetch(generateBareUrl(integrationType, g.ebconfig.integration), { 119 | method: "POST", 120 | headers: { 121 | 'Eb-Post-Req': POST_TYPES.HANDSHAKE, 122 | 'Accept': 'application/json', 123 | 'Content-Type': 'application/json' 124 | }, 125 | body: JSON.stringify({ 126 | version: g.ebconfig.version, 127 | session: g.session, 128 | instance: g.instance, 129 | userID, 130 | password 131 | }) 132 | }); 133 | 134 | const resData = await res.json(); 135 | 136 | if (resData.token) { 137 | g.token = resData.token; 138 | g.refreshToken = resData.refreshToken; 139 | g.newTokenCallback(); 140 | g.userID = resData.userID; 141 | g.mounted = true; 142 | const [validTokenRes, { hash }, { fromUtf8 }] = await Promise.all([tokenPost(POST_TYPES.VALID_TOKEN), import('fast-sha256'), import('@aws-sdk/util-utf8-browser')]) 143 | const elapsed = Date.now() - t1; 144 | if (validTokenRes.success) { 145 | log("Valid auth initiation in " + elapsed + "ms"); 146 | if (g.analyticsEnabled && g.analyticsEventsToTrack.login) { 147 | const hashOut = hash(fromUtf8(g.GA_USER_ID_SALT + resData.userID)); 148 | const hexHash = Array.prototype.map.call(hashOut, x => ('00' + x.toString(16)).slice(-2)).join(''); 149 | g.analyticsIdentify(hexHash); 150 | g.analyticsEvent('login', { method: "Easybase" }); 151 | } 152 | return { 153 | success: true, 154 | message: "Successfully signed in user" 155 | }; 156 | } else { 157 | return { 158 | success: false, 159 | message: "Could not sign in user" 160 | }; 161 | } 162 | } else { 163 | return { 164 | success: false, 165 | message: "Could not sign in user", 166 | errorCode: resData.ErrorCode || undefined 167 | }; 168 | } 169 | } catch (error) { 170 | return { 171 | success: false, 172 | message: error.message || "Could not sign in user", 173 | errorCode: error.errorCode || undefined 174 | }; 175 | } 176 | } 177 | 178 | const resetUserPassword = async (currentPassword: string, newPassword: string): Promise => { 179 | if (typeof newPassword !== "string" || newPassword.length > 100) { 180 | return { 181 | success: false, 182 | message: "newPassword must be of type string" 183 | }; 184 | } 185 | 186 | if (typeof currentPassword !== "string" || currentPassword.length > 100) { 187 | return { 188 | success: false, 189 | message: "currentPassword must be of type string" 190 | }; 191 | } 192 | 193 | try { 194 | const setAttrsRes = await tokenPost(POST_TYPES.RESET_PASSWORD, { currentPassword, newPassword }); 195 | g.analyticsEnabled && g.analyticsEventsToTrack.reset_user_password && g.analyticsEvent('reset_user_password'); 196 | return { 197 | success: setAttrsRes.success, 198 | message: JSON.stringify(setAttrsRes.data) 199 | }; 200 | } catch (error) { 201 | return { 202 | success: false, 203 | message: error.message || "Error", 204 | errorCode: error.errorCode || undefined 205 | }; 206 | } 207 | } 208 | 209 | const userID = (): string | undefined => g.userID || undefined; 210 | 211 | const signOut = (): void => { 212 | g.token = ""; 213 | g.newTokenCallback(); 214 | g.userID = undefined; 215 | } 216 | 217 | const initAuth = async (): Promise => { 218 | const t1 = Date.now(); 219 | g.session = Math.floor(100000000 + Math.random() * 900000000); 220 | 221 | log(`Handshaking on${g.instance} instance`); 222 | 223 | const integrationType = g.ebconfig.integration.split("-")[0].toUpperCase() === "PROJECT" ? "PROJECT" : "REACT"; 224 | 225 | try { 226 | const res = await fetch(generateBareUrl(integrationType, g.ebconfig.integration), { 227 | method: "POST", 228 | headers: { 229 | 'Eb-Post-Req': POST_TYPES.HANDSHAKE, 230 | 'Accept': 'application/json', 231 | 'Content-Type': 'application/json' 232 | }, 233 | body: JSON.stringify({ 234 | version: g.ebconfig.version, 235 | tt: g.ebconfig.tt, 236 | session: g.session, 237 | instance: g.instance 238 | }) 239 | }); 240 | 241 | const resData = await res.json(); 242 | 243 | if (resData.token) { 244 | g.token = resData.token; 245 | g.mounted = true; 246 | const validTokenRes = await tokenPost(POST_TYPES.VALID_TOKEN); 247 | const elapsed = Date.now() - t1; 248 | if (validTokenRes.success) { 249 | log("Valid auth initiation in " + elapsed + "ms"); 250 | return true; 251 | } else { 252 | return false; 253 | } 254 | } else { 255 | return false; 256 | } 257 | } catch (error) { 258 | console.error(error); 259 | return false; 260 | } 261 | } 262 | 263 | const tokenPost = async (postType: POST_TYPES, body?: {}): Promise => { 264 | if (!g.mounted) { 265 | await initAuth(); 266 | } 267 | 268 | const integrationType = g.ebconfig.integration.split("-")[0].toUpperCase() === "PROJECT" ? "PROJECT" : "REACT"; 269 | 270 | const res = await fetch(generateBareUrl(integrationType, g.ebconfig.integration), { 271 | method: "POST", 272 | headers: { 273 | 'Eb-Post-Req': postType, 274 | 'Accept': 'application/json', 275 | 'Content-Type': 'application/json' 276 | }, 277 | body: JSON.stringify({ 278 | _auth: generateAuthBody(), 279 | ...body 280 | }) 281 | }); 282 | 283 | const resData = await res.json(); 284 | 285 | if ({}.hasOwnProperty.call(resData, 'ErrorCode') || {}.hasOwnProperty.call(resData, 'code')) { 286 | if (resData.ErrorCode === "TokenExpired") { 287 | if (integrationType === "PROJECT") { 288 | try { 289 | const req_res = await tokenPost(POST_TYPES.REQUEST_TOKEN, { 290 | refreshToken: g.refreshToken, 291 | token: g.token 292 | }); 293 | if (req_res.success) { 294 | g.token = req_res.data.token 295 | g.newTokenCallback(); 296 | return tokenPost(postType, body); 297 | } else { 298 | throw new Error(req_res.data || "Error"); 299 | } 300 | } catch (error) { 301 | _clearTokens(); 302 | return { 303 | success: false, 304 | data: error.message || error 305 | } 306 | } 307 | } else { 308 | await initAuth(); 309 | } 310 | return tokenPost(postType, body); 311 | } else { 312 | const err = new Error(resData.body || resData.ErrorCode || resData.code || "Error"); 313 | (err as any).errorCode = resData.ErrorCode || resData.code; 314 | throw err; 315 | } 316 | } else { 317 | return { 318 | success: resData.success, 319 | data: resData.body 320 | } 321 | } 322 | } 323 | 324 | const tokenPostAttachment = async (formData: FormData, customHeaders: {}): Promise => { 325 | if (!g.mounted) { 326 | await initAuth(); 327 | } 328 | 329 | const regularAuthbody = generateAuthBody(); 330 | 331 | const attachmentAuth = { 332 | 'Eb-token': regularAuthbody.token, 333 | 'Eb-token-time': regularAuthbody.token_time, 334 | 'Eb-now': regularAuthbody.now 335 | }; 336 | 337 | const integrationType = g.ebconfig.integration.split("-")[0].toUpperCase() === "PROJECT" ? "PROJECT" : "REACT"; 338 | 339 | const res = await fetch(generateBareUrl(integrationType, g.ebconfig.integration), { 340 | method: "POST", 341 | headers: { 342 | 'Eb-Post-Req': POST_TYPES.UPLOAD_ATTACHMENT, 343 | ...customHeaders, 344 | ...attachmentAuth 345 | }, 346 | body: formData 347 | }); 348 | 349 | const resData = await res.json(); 350 | 351 | if ({}.hasOwnProperty.call(resData, 'ErrorCode') || {}.hasOwnProperty.call(resData, 'code')) { 352 | if (resData.ErrorCode === "TokenExpired") { 353 | if (integrationType === "PROJECT") { 354 | try { 355 | const req_res = await tokenPost(POST_TYPES.REQUEST_TOKEN, { 356 | refreshToken: g.refreshToken, 357 | token: g.token 358 | }); 359 | 360 | if (req_res.success) { 361 | g.token = req_res.data.token 362 | g.newTokenCallback(); 363 | return tokenPostAttachment(formData, customHeaders); 364 | } else { 365 | throw new Error(req_res.data || "Error"); 366 | } 367 | } catch (error) { 368 | _clearTokens(); 369 | return { 370 | success: false, 371 | data: error.message || error 372 | } 373 | } 374 | } else { 375 | await initAuth(); 376 | } 377 | return tokenPostAttachment(formData, customHeaders); 378 | } else { 379 | const err = new Error(resData.body || resData.ErrorCode || resData.code || "Error"); 380 | (err as any).errorCode = resData.ErrorCode || resData.code; 381 | throw err; 382 | } 383 | } else { 384 | return { 385 | success: resData.success, 386 | data: resData.body 387 | } 388 | } 389 | } 390 | 391 | return { 392 | initAuth, 393 | tokenPost, 394 | tokenPostAttachment, 395 | signUp, 396 | setUserAttribute, 397 | getUserAttributes, 398 | signIn, 399 | signOut, 400 | resetUserPassword, 401 | forgotPassword, 402 | forgotPasswordConfirm, 403 | userID 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/EasybaseProvider/db.ts: -------------------------------------------------------------------------------- 1 | import _g from "./g"; 2 | import easyqb from 'easyqb'; 3 | import { SQW } from "easyqb/types/sq"; 4 | import { NewExpression } from "easyqb/types/expression"; 5 | import authFactory from "./auth"; 6 | import { POST_TYPES, DB_STATUS, Globals, EXECUTE_COUNT, FileFromURI, StatusResponse } from "./types"; 7 | import imageExtensions from "./assets/image-extensions.json"; 8 | import videoExtensions from "./assets/video-extensions.json"; 9 | 10 | interface IdbFactory { 11 | db: (tableName?: string, userAssociatedRecordsOnly?: boolean) => SQW; 12 | dbEventListener: (callback: (status?: DB_STATUS, queryType?: string, executeCount?: EXECUTE_COUNT, tableName?: string | null, returned?: any) => void) => () => void; 13 | e: NewExpression; 14 | setImage(recordKey: string, columnName: string, image: File | FileFromURI, tableName?: string): Promise; 15 | setVideo(recordKey: string, columnName: string, video: File | FileFromURI, tableName?: string): Promise; 16 | setFile(recordKey: string, columnName: string, file: File | FileFromURI, tableName?: string): Promise; 17 | } 18 | 19 | interface IUploadFile { 20 | recordKey: string; 21 | columnName: string; 22 | attachment: File | FileFromURI; 23 | type: "image" | "video" | "file" 24 | tableName?: string; 25 | } 26 | 27 | export default function dbFactory(globals?: Globals): IdbFactory { 28 | const g = globals || _g; 29 | const { tokenPost, tokenPostAttachment } = authFactory(g); 30 | let _listenerIndex = 0; 31 | 32 | const _listeners: Record void> = {}; 33 | 34 | function _runListeners(...params: any[]) { 35 | for (const cb of Object.values(_listeners)) { 36 | cb(...params) 37 | } 38 | } 39 | 40 | const dbEventListener = (callback: (status?: DB_STATUS, queryType?: string, executeCount?: EXECUTE_COUNT, tableName?: string | null, returned?: any) => void): () => void => { 41 | const currKey = '' + _listenerIndex++; 42 | _listeners[currKey] = callback; 43 | return () => { 44 | delete _listeners[currKey] 45 | } 46 | } 47 | 48 | const allCallback = async (trx: any, tableName: string, userAssociatedRecordsOnly?: boolean): Promise[] | number[]> => { 49 | trx.count = "all"; 50 | trx.tableName = tableName; 51 | if (userAssociatedRecordsOnly) trx.userAssociatedRecordsOnly = userAssociatedRecordsOnly; 52 | _runListeners(DB_STATUS.PENDING, trx.type, EXECUTE_COUNT.ALL, tableName !== "untable" ? tableName : null); 53 | try { 54 | const res = await tokenPost(POST_TYPES.EASY_QB, trx); 55 | if (res.success) { 56 | g.analyticsEnabled && g.analyticsEventsToTrack.db_all && g.analyticsEvent('db_all', { tableName: tableName !== "untable" ? tableName : undefined, type: trx.type }); 57 | _runListeners(DB_STATUS.SUCCESS, trx.type, EXECUTE_COUNT.ALL, tableName !== "untable" ? tableName : null, res.data); 58 | return res.data; 59 | } else { 60 | _runListeners(DB_STATUS.ERROR, trx.type, EXECUTE_COUNT.ALL, tableName !== "untable" ? tableName : null); 61 | return res; 62 | } 63 | } catch (error) { 64 | console.warn(error) 65 | _runListeners(DB_STATUS.ERROR, trx.type, EXECUTE_COUNT.ALL, tableName !== "untable" ? tableName : null); 66 | return []; 67 | } 68 | } 69 | 70 | const oneCallback = async (trx: any, tableName: string, userAssociatedRecordsOnly?: boolean): Promise | number> => { 71 | trx.count = "one"; 72 | trx.tableName = tableName; 73 | if (userAssociatedRecordsOnly) trx.userAssociatedRecordsOnly = userAssociatedRecordsOnly; 74 | _runListeners(DB_STATUS.PENDING, trx.type, EXECUTE_COUNT.ONE, tableName !== "untable" ? tableName : null); 75 | try { 76 | const res = await tokenPost(POST_TYPES.EASY_QB, trx); 77 | if (res.success) { 78 | g.analyticsEnabled && g.analyticsEventsToTrack.db_one && g.analyticsEvent('db_one', { tableName: tableName !== "untable" ? tableName : undefined, type: trx.type }); 79 | _runListeners(DB_STATUS.SUCCESS, trx.type, EXECUTE_COUNT.ONE, tableName !== "untable" ? tableName : null, res.data); 80 | return res.data; 81 | } else { 82 | _runListeners(DB_STATUS.ERROR, trx.type, EXECUTE_COUNT.ONE, tableName !== "untable" ? tableName : null); 83 | return res; 84 | } 85 | } catch (error) { 86 | console.warn(error) 87 | _runListeners(DB_STATUS.ERROR, trx.type, EXECUTE_COUNT.ONE, tableName !== "untable" ? tableName : null); 88 | return {}; 89 | } 90 | } 91 | 92 | const db = (tableName?: string, userAssociatedRecordsOnly?: boolean) => { 93 | if (tableName && typeof tableName === "string") { 94 | return easyqb({ allCallback, oneCallback, userAssociatedRecordsOnly, tableName: tableName.toUpperCase() })(tableName.replace(/[^0-9a-zA-Z]/g, '_').toUpperCase()); 95 | } else { 96 | return easyqb({ allCallback, oneCallback, userAssociatedRecordsOnly, tableName: "untable" })("untable"); 97 | } 98 | } 99 | 100 | const _setAttachment = async ({ recordKey, columnName, attachment, tableName, type }: IUploadFile): Promise => { 101 | const ext: string = attachment.name.split(".").pop()!.toLowerCase(); 102 | 103 | // Similar pattern as db() naming 104 | let fixedTableName: string; 105 | if (tableName && typeof tableName === "string") { 106 | fixedTableName = tableName.toUpperCase(); 107 | } else { 108 | fixedTableName = "untable"; 109 | } 110 | 111 | if (type === "image" && !imageExtensions.includes(ext)) { 112 | return { 113 | success: false, 114 | message: "Image files must have a proper image extension in the file name" 115 | }; 116 | } 117 | 118 | if (type === "video" && !videoExtensions.includes(ext)) { 119 | return { 120 | success: false, 121 | message: "Video files must have a proper video extension in the file name" 122 | }; 123 | } 124 | 125 | const formData = new FormData(); 126 | 127 | formData.append("file", attachment as Blob); 128 | formData.append("name", attachment.name); 129 | 130 | const customHeaders = { 131 | 'Eb-upload-type': type, 132 | 'Eb-column-name': columnName, 133 | 'Eb-record-id': recordKey, 134 | 'Eb-table-name': fixedTableName 135 | } 136 | 137 | try { 138 | const res = await tokenPostAttachment(formData, customHeaders); 139 | if (res.success) { 140 | g.analyticsEnabled && g.analyticsEventsToTrack.db_one && g.analyticsEvent('db_one', { tableName: fixedTableName !== "untable" ? fixedTableName : undefined, type: "update" }); 141 | _runListeners(DB_STATUS.SUCCESS, "update", EXECUTE_COUNT.ONE, fixedTableName !== "untable" ? fixedTableName : null, res.data); 142 | } else { 143 | _runListeners(DB_STATUS.ERROR, "update", EXECUTE_COUNT.ONE, fixedTableName !== "untable" ? fixedTableName : null); 144 | } 145 | return { 146 | message: res.data, 147 | success: res.success 148 | }; 149 | } catch (error) { 150 | console.warn(error) 151 | _runListeners(DB_STATUS.ERROR, "update", EXECUTE_COUNT.ONE, fixedTableName !== "untable" ? fixedTableName : null); 152 | return { 153 | message: "", 154 | success: false, 155 | }; 156 | } 157 | } 158 | 159 | const setImage = async (recordKey: string, columnName: string, image: File | FileFromURI, tableName?: string) => _setAttachment({ 160 | recordKey, 161 | columnName, 162 | tableName, 163 | attachment: image, 164 | type: "image" 165 | }); 166 | 167 | const setVideo = async (recordKey: string, columnName: string, video: File | FileFromURI, tableName?: string) => _setAttachment({ 168 | recordKey, 169 | columnName, 170 | tableName, 171 | attachment: video, 172 | type: "video" 173 | }); 174 | 175 | const setFile = async (recordKey: string, columnName: string, file: File | FileFromURI, tableName?: string) => _setAttachment({ 176 | recordKey, 177 | columnName, 178 | tableName, 179 | attachment: file, 180 | type: "file" 181 | }); 182 | 183 | return { 184 | db, 185 | dbEventListener, 186 | e: easyqb().e, 187 | setImage, 188 | setFile, 189 | setVideo 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/EasybaseProvider/g.ts: -------------------------------------------------------------------------------- 1 | import { Ebconfig, EasybaseProviderPropsOptions, Globals, EasybaseProviderProps, GoogleAnalyticsEvents } from "./types"; 2 | 3 | namespace GlobalNamespace { 4 | export let ebconfig: Ebconfig; 5 | export let token: string; 6 | export let refreshToken: string; 7 | export let session: number; 8 | export let options: EasybaseProviderPropsOptions; 9 | export let instance: "Node" | "React" | "React Native"; 10 | export let mounted: boolean; 11 | export let newTokenCallback: () => {}; 12 | export let userID: string; 13 | export let analyticsEnabled: boolean; 14 | export let analyticsEventsToTrack: GoogleAnalyticsEvents; 15 | export let analyticsEvent: () => {}; 16 | export let analyticsIdentify: () => {}; 17 | export let GA_USER_ID_SALT: string; // https://support.google.com/analytics/answer/6366371?hl=en#hashed 18 | } 19 | 20 | const _g: Globals = { ...GlobalNamespace }; 21 | 22 | export default _g; 23 | 24 | export function gFactory({ ebconfig, options }: EasybaseProviderProps): Globals { 25 | const optionsObj = { ...options }; // Forces undefined to empty object 26 | const gaTrackingObj = options ? options.googleAnalyticsEventTracking : {}; 27 | const defaultG = { 28 | options: optionsObj, 29 | ebconfig: ebconfig, 30 | GA_USER_ID_SALT: "m83WnAPrq", 31 | analyticsEventsToTrack: { 32 | login: true, 33 | sign_up: true, 34 | forgot_password: true, 35 | forgot_password_confirm: true, 36 | reset_user_password: true, 37 | ...gaTrackingObj 38 | } 39 | } 40 | return { ...GlobalNamespace, ...defaultG } as Globals; 41 | } 42 | -------------------------------------------------------------------------------- /src/EasybaseProvider/object-observer.js: -------------------------------------------------------------------------------- 1 | const 2 | INSERT = 'insert', 3 | UPDATE = 'update', 4 | DELETE = 'delete', 5 | REVERSE = 'reverse', 6 | SHUFFLE = 'shuffle', 7 | oMetaKey = Symbol('observable-meta-key'), 8 | validObservableOptionKeys = { async: 1 }, 9 | validObserverOptionKeys = { path: 1, pathsOf: 1, pathsFrom: 1 }, 10 | processObserveOptions = function processObserveOptions(options) { 11 | const result = {}; 12 | if (options.path !== undefined) { 13 | if (typeof options.path !== 'string' || options.path === '') { 14 | throw new Error('"path" option, if/when provided, MUST be a non-empty string'); 15 | } 16 | result.path = options.path; 17 | } 18 | if (options.pathsOf !== undefined) { 19 | if (options.path) { 20 | throw new Error('"pathsOf" option MAY NOT be specified together with "path" option'); 21 | } 22 | if (typeof options.pathsOf !== 'string') { 23 | throw new Error('"pathsOf" option, if/when provided, MUST be a string (MAY be empty)'); 24 | } 25 | result.pathsOf = options.pathsOf.split('.').filter(n => n); 26 | } 27 | if (options.pathsFrom !== undefined) { 28 | if (options.path || options.pathsOf) { 29 | throw new Error('"pathsFrom" option MAY NOT be specified together with "path"/"pathsOf" option/s'); 30 | } 31 | if (typeof options.pathsFrom !== 'string' || options.pathsFrom === '') { 32 | throw new Error('"pathsFrom" option, if/when provided, MUST be a non-empty string'); 33 | } 34 | result.pathsFrom = options.pathsFrom; 35 | } 36 | const invalidOptions = Object.keys(options).filter(option => !validObserverOptionKeys.hasOwnProperty(option)); 37 | if (invalidOptions.length) { 38 | throw new Error(`'${invalidOptions.join(', ')}' is/are not a valid observer option/s`); 39 | } 40 | return result; 41 | }, 42 | observe = function observe(observer, options) { 43 | if (typeof observer !== 'function') { 44 | throw new Error(`observer MUST be a function, got '${observer}'`); 45 | } 46 | 47 | const 48 | oMeta = this[oMetaKey], 49 | observers = oMeta.observers; 50 | if (!observers.some(o => o[0] === observer)) { 51 | let opts; 52 | if (options) { 53 | opts = processObserveOptions(options); 54 | } else { 55 | opts = {}; 56 | } 57 | observers.push([observer, opts]); 58 | } else { 59 | console.warn('observer may be bound to an observable only once; will NOT rebind'); 60 | } 61 | }, 62 | unobserve = function unobserve() { 63 | const oMeta = this[oMetaKey]; 64 | const observers = oMeta.observers; 65 | let ol = observers.length; 66 | if (ol) { 67 | let al = arguments.length; 68 | if (al) { 69 | while (al--) { 70 | let i = ol; 71 | while (i--) { 72 | if (observers[i][0] === arguments[al]) { 73 | observers.splice(i, 1); 74 | ol--; 75 | } 76 | } 77 | } 78 | } else { 79 | observers.splice(0); 80 | } 81 | } 82 | }, 83 | propertiesBluePrint = { [oMetaKey]: { value: null }, observe: { value: observe }, unobserve: { value: unobserve } }, 84 | prepareObject = function prepareObject(source, oMeta) { 85 | propertiesBluePrint[oMetaKey].value = oMeta; 86 | const target = Object.defineProperties({}, propertiesBluePrint); 87 | for (const key of Object.keys(source)) { 88 | target[key] = getObservedOf(source[key], key, oMeta); 89 | } 90 | return target; 91 | }, 92 | prepareArray = function prepareArray(source, oMeta) { 93 | let i = 0, l = source.length; 94 | propertiesBluePrint[oMetaKey].value = oMeta; 95 | const target = Object.defineProperties(new Array(l), propertiesBluePrint); 96 | for (; i < l; i++) { 97 | target[i] = getObservedOf(source[i], i, oMeta); 98 | } 99 | return target; 100 | }, 101 | prepareTypedArray = function prepareTypedArray(source, oMeta) { 102 | propertiesBluePrint[oMetaKey].value = oMeta; 103 | Object.defineProperties(source, propertiesBluePrint); 104 | return source; 105 | }, 106 | filterChanges = function filterChanges(options, changes) { 107 | let result = changes; 108 | if (options.path) { 109 | const oPath = options.path; 110 | result = changes.filter(change => 111 | change.path.join('.') === oPath 112 | ); 113 | } else if (options.pathsOf) { 114 | const oPathsOf = options.pathsOf; 115 | result = changes.filter(change => 116 | change.path.length === oPathsOf.length + 1 || 117 | (change.path.length === oPathsOf.length && (change.type === REVERSE || change.type === SHUFFLE)) 118 | ); 119 | } else if (options.pathsFrom) { 120 | const oPathsFrom = options.pathsFrom; 121 | result = changes.filter(change => 122 | change.path.join('.').startsWith(oPathsFrom) 123 | ); 124 | } 125 | return result; 126 | }, 127 | callObserverSafe = function callObserverSafe(listener, changes) { 128 | try { 129 | listener(changes); 130 | } catch (e) { 131 | console.error(`failed to notify listener ${listener} with ${changes}`, e); 132 | } 133 | }, 134 | callObserversFromMT = function callObserversFromMT() { 135 | const batches = this.batches; 136 | this.batches = null; 137 | for (const [listener, options] of batches) { 138 | callObserverSafe(listener, options); 139 | } 140 | }, 141 | callObservers = function callObservers(oMeta, changes) { 142 | let currentObservable = oMeta; 143 | let observers, target, options, relevantChanges, i, newPath, tmp; 144 | const l = changes.length; 145 | do { 146 | observers = currentObservable.observers; 147 | i = observers.length; 148 | while (i--) { 149 | [target, options] = observers[i]; 150 | relevantChanges = filterChanges(options, changes); 151 | 152 | if (relevantChanges.length) { 153 | if (currentObservable.options.async) { 154 | // this is the async dispatch handling 155 | if (!currentObservable.batches) { 156 | currentObservable.batches = []; 157 | queueMicrotask(callObserversFromMT.bind(currentObservable)); 158 | } 159 | let rb = currentObservable.batches.find(b => b[0] === target); 160 | if (!rb) { 161 | rb = [target, []]; 162 | currentObservable.batches.push(rb); 163 | } 164 | Array.prototype.push.apply(rb[1], relevantChanges); 165 | } else { 166 | // this is the naive straight forward synchronous dispatch 167 | callObserverSafe(target, relevantChanges); 168 | } 169 | } 170 | } 171 | 172 | let tmpa; 173 | if (currentObservable.parent) { 174 | tmpa = new Array(l); 175 | for (let i = 0; i < l; i++) { 176 | tmp = changes[i]; 177 | newPath = [currentObservable.ownKey, ...tmp.path]; 178 | tmpa[i] = { 179 | type: tmp.type, 180 | path: newPath, 181 | value: tmp.value, 182 | oldValue: tmp.oldValue, 183 | object: tmp.object 184 | }; 185 | } 186 | changes = tmpa; 187 | currentObservable = currentObservable.parent; 188 | } else { 189 | currentObservable = null; 190 | } 191 | } while (currentObservable); 192 | }, 193 | getObservedOf = function getObservedOf(item, key, parent) { 194 | if (!item || typeof item !== 'object') { 195 | return item; 196 | } else if (Array.isArray(item)) { 197 | return new ArrayOMeta({ target: item, ownKey: key, parent: parent }).proxy; 198 | } else if (ArrayBuffer.isView(item)) { 199 | return new TypedArrayOMeta({ target: item, ownKey: key, parent: parent }).proxy; 200 | } else if (item instanceof Date || item instanceof Error) { 201 | return item; 202 | } else { 203 | return new ObjectOMeta({ target: item, ownKey: key, parent: parent }).proxy; 204 | } 205 | }, 206 | proxiedPop = function proxiedPop() { 207 | const oMeta = this[oMetaKey], 208 | target = oMeta.target, 209 | poppedIndex = target.length - 1; 210 | 211 | let popResult = target.pop(); 212 | if (popResult && typeof popResult === 'object') { 213 | const tmpObserved = popResult[oMetaKey]; 214 | if (tmpObserved) { 215 | popResult = tmpObserved.detach(); 216 | } 217 | } 218 | 219 | const changes = [{ type: DELETE, path: [poppedIndex], oldValue: popResult, object: this }]; 220 | callObservers(oMeta, changes); 221 | 222 | return popResult; 223 | }, 224 | proxiedPush = function proxiedPush() { 225 | const 226 | oMeta = this[oMetaKey], 227 | target = oMeta.target, 228 | l = arguments.length, 229 | pushContent = new Array(l), 230 | initialLength = target.length; 231 | 232 | for (let i = 0; i < l; i++) { 233 | pushContent[i] = getObservedOf(arguments[i], initialLength + i, oMeta); 234 | } 235 | const pushResult = Reflect.apply(target.push, target, pushContent); 236 | 237 | const changes = []; 238 | for (let i = initialLength, l = target.length; i < l; i++) { 239 | changes[i - initialLength] = { type: INSERT, path: [i], value: target[i], object: this }; 240 | } 241 | callObservers(oMeta, changes); 242 | 243 | return pushResult; 244 | }, 245 | proxiedShift = function proxiedShift() { 246 | const 247 | oMeta = this[oMetaKey], 248 | target = oMeta.target; 249 | let shiftResult, i, l, item, tmpObserved; 250 | 251 | shiftResult = target.shift(); 252 | if (shiftResult && typeof shiftResult === 'object') { 253 | tmpObserved = shiftResult[oMetaKey]; 254 | if (tmpObserved) { 255 | shiftResult = tmpObserved.detach(); 256 | } 257 | } 258 | 259 | // update indices of the remaining items 260 | for (i = 0, l = target.length; i < l; i++) { 261 | item = target[i]; 262 | if (item && typeof item === 'object') { 263 | tmpObserved = item[oMetaKey]; 264 | if (tmpObserved) { 265 | tmpObserved.ownKey = i; 266 | } 267 | } 268 | } 269 | 270 | const changes = [{ type: DELETE, path: [0], oldValue: shiftResult, object: this }]; 271 | callObservers(oMeta, changes); 272 | 273 | return shiftResult; 274 | }, 275 | proxiedUnshift = function proxiedUnshift() { 276 | const 277 | oMeta = this[oMetaKey], 278 | target = oMeta.target, 279 | al = arguments.length, 280 | unshiftContent = new Array(al); 281 | 282 | for (let i = 0; i < al; i++) { 283 | unshiftContent[i] = getObservedOf(arguments[i], i, oMeta); 284 | } 285 | const unshiftResult = Reflect.apply(target.unshift, target, unshiftContent); 286 | 287 | for (let i = 0, l = target.length, item; i < l; i++) { 288 | item = target[i]; 289 | if (item && typeof item === 'object') { 290 | const tmpObserved = item[oMetaKey]; 291 | if (tmpObserved) { 292 | tmpObserved.ownKey = i; 293 | } 294 | } 295 | } 296 | 297 | // publish changes 298 | const l = unshiftContent.length; 299 | const changes = new Array(l); 300 | for (let i = 0; i < l; i++) { 301 | changes[i] = { type: INSERT, path: [i], value: target[i], object: this }; 302 | } 303 | callObservers(oMeta, changes); 304 | 305 | return unshiftResult; 306 | }, 307 | proxiedReverse = function proxiedReverse() { 308 | const 309 | oMeta = this[oMetaKey], 310 | target = oMeta.target; 311 | let i, l, item; 312 | 313 | target.reverse(); 314 | for (i = 0, l = target.length; i < l; i++) { 315 | item = target[i]; 316 | if (item && typeof item === 'object') { 317 | const tmpObserved = item[oMetaKey]; 318 | if (tmpObserved) { 319 | tmpObserved.ownKey = i; 320 | } 321 | } 322 | } 323 | 324 | const changes = [{ type: REVERSE, path: [], object: this }]; 325 | callObservers(oMeta, changes); 326 | 327 | return this; 328 | }, 329 | proxiedSort = function proxiedSort(comparator) { 330 | const 331 | oMeta = this[oMetaKey], 332 | target = oMeta.target; 333 | let i, l, item; 334 | 335 | target.sort(comparator); 336 | for (i = 0, l = target.length; i < l; i++) { 337 | item = target[i]; 338 | if (item && typeof item === 'object') { 339 | const tmpObserved = item[oMetaKey]; 340 | if (tmpObserved) { 341 | tmpObserved.ownKey = i; 342 | } 343 | } 344 | } 345 | 346 | const changes = [{ type: SHUFFLE, path: [], object: this }]; 347 | callObservers(oMeta, changes); 348 | 349 | return this; 350 | }, 351 | proxiedFill = function proxiedFill(filVal, start, end) { 352 | const 353 | oMeta = this[oMetaKey], 354 | target = oMeta.target, 355 | changes = [], 356 | tarLen = target.length, 357 | prev = target.slice(0); 358 | start = start === undefined ? 0 : (start < 0 ? Math.max(tarLen + start, 0) : Math.min(start, tarLen)); 359 | end = end === undefined ? tarLen : (end < 0 ? Math.max(tarLen + end, 0) : Math.min(end, tarLen)); 360 | 361 | if (start < tarLen && end > start) { 362 | target.fill(filVal, start, end); 363 | 364 | let tmpObserved; 365 | for (let i = start, item, tmpTarget; i < end; i++) { 366 | item = target[i]; 367 | target[i] = getObservedOf(item, i, oMeta); 368 | if (prev.hasOwnProperty(i)) { 369 | tmpTarget = prev[i]; 370 | if (tmpTarget && typeof tmpTarget === 'object') { 371 | tmpObserved = tmpTarget[oMetaKey]; 372 | if (tmpObserved) { 373 | tmpTarget = tmpObserved.detach(); 374 | } 375 | } 376 | 377 | changes.push({ type: UPDATE, path: [i], value: target[i], oldValue: tmpTarget, object: this }); 378 | } else { 379 | changes.push({ type: INSERT, path: [i], value: target[i], object: this }); 380 | } 381 | } 382 | 383 | callObservers(oMeta, changes); 384 | } 385 | 386 | return this; 387 | }, 388 | proxiedCopyWithin = function proxiedCopyWithin(dest, start, end) { 389 | const 390 | oMeta = this[oMetaKey], 391 | target = oMeta.target, 392 | tarLen = target.length; 393 | dest = dest < 0 ? Math.max(tarLen + dest, 0) : dest; 394 | start = start === undefined ? 0 : (start < 0 ? Math.max(tarLen + start, 0) : Math.min(start, tarLen)); 395 | end = end === undefined ? tarLen : (end < 0 ? Math.max(tarLen + end, 0) : Math.min(end, tarLen)); 396 | const len = Math.min(end - start, tarLen - dest); 397 | 398 | if (dest < tarLen && dest !== start && len > 0) { 399 | const 400 | prev = target.slice(0), 401 | changes = []; 402 | 403 | target.copyWithin(dest, start, end); 404 | 405 | for (let i = dest, nItem, oItem, tmpObserved; i < dest + len; i++) { 406 | // update newly placed observables, if any 407 | nItem = target[i]; 408 | if (nItem && typeof nItem === 'object') { 409 | nItem = getObservedOf(nItem, i, oMeta); 410 | target[i] = nItem; 411 | } 412 | 413 | // detach overridden observables, if any 414 | oItem = prev[i]; 415 | if (oItem && typeof oItem === 'object') { 416 | tmpObserved = oItem[oMetaKey]; 417 | if (tmpObserved) { 418 | oItem = tmpObserved.detach(); 419 | } 420 | } 421 | 422 | if (typeof nItem !== 'object' && nItem === oItem) { 423 | continue; 424 | } 425 | changes.push({ type: UPDATE, path: [i], value: nItem, oldValue: oItem, object: this }); 426 | } 427 | 428 | callObservers(oMeta, changes); 429 | } 430 | 431 | return this; 432 | }, 433 | proxiedSplice = function proxiedSplice() { 434 | const 435 | oMeta = this[oMetaKey], 436 | target = oMeta.target, 437 | splLen = arguments.length, 438 | spliceContent = new Array(splLen), 439 | tarLen = target.length; 440 | 441 | // observify the newcomers 442 | for (let i = 0; i < splLen; i++) { 443 | spliceContent[i] = getObservedOf(arguments[i], i, oMeta); 444 | } 445 | 446 | // calculate pointers 447 | const 448 | startIndex = splLen === 0 ? 0 : (spliceContent[0] < 0 ? tarLen + spliceContent[0] : spliceContent[0]), 449 | removed = splLen < 2 ? tarLen - startIndex : spliceContent[1], 450 | inserted = Math.max(splLen - 2, 0), 451 | spliceResult = Reflect.apply(target.splice, target, spliceContent), 452 | newTarLen = target.length; 453 | 454 | // reindex the paths 455 | let tmpObserved; 456 | for (let i = 0, item; i < newTarLen; i++) { 457 | item = target[i]; 458 | if (item && typeof item === 'object') { 459 | tmpObserved = item[oMetaKey]; 460 | if (tmpObserved) { 461 | tmpObserved.ownKey = i; 462 | } 463 | } 464 | } 465 | 466 | // detach removed objects 467 | let i, l, item; 468 | for (i = 0, l = spliceResult.length; i < l; i++) { 469 | item = spliceResult[i]; 470 | if (item && typeof item === 'object') { 471 | tmpObserved = item[oMetaKey]; 472 | if (tmpObserved) { 473 | spliceResult[i] = tmpObserved.detach(); 474 | } 475 | } 476 | } 477 | 478 | const changes = []; 479 | let index; 480 | for (index = 0; index < removed; index++) { 481 | if (index < inserted) { 482 | changes.push({ type: UPDATE, path: [startIndex + index], value: target[startIndex + index], oldValue: spliceResult[index], object: this }); 483 | } else { 484 | changes.push({ type: DELETE, path: [startIndex + index], oldValue: spliceResult[index], object: this }); 485 | } 486 | } 487 | for (; index < inserted; index++) { 488 | changes.push({ type: INSERT, path: [startIndex + index], value: target[startIndex + index], object: this }); 489 | } 490 | callObservers(oMeta, changes); 491 | 492 | return spliceResult; 493 | }, 494 | proxiedTypedArraySet = function proxiedTypedArraySet(source, offset) { 495 | const 496 | oMeta = this[oMetaKey], 497 | target = oMeta.target, 498 | souLen = source.length, 499 | prev = target.slice(0); 500 | offset = offset || 0; 501 | 502 | target.set(source, offset); 503 | const changes = new Array(souLen); 504 | for (let i = offset; i < (souLen + offset); i++) { 505 | changes[i - offset] = { type: UPDATE, path: [i], value: target[i], oldValue: prev[i], object: this }; 506 | } 507 | 508 | callObservers(oMeta, changes); 509 | }, 510 | proxiedArrayMethods = { 511 | pop: proxiedPop, 512 | push: proxiedPush, 513 | shift: proxiedShift, 514 | unshift: proxiedUnshift, 515 | reverse: proxiedReverse, 516 | sort: proxiedSort, 517 | fill: proxiedFill, 518 | copyWithin: proxiedCopyWithin, 519 | splice: proxiedSplice 520 | }, 521 | proxiedTypedArrayMethods = { 522 | reverse: proxiedReverse, 523 | sort: proxiedSort, 524 | fill: proxiedFill, 525 | copyWithin: proxiedCopyWithin, 526 | set: proxiedTypedArraySet 527 | }; 528 | 529 | class OMetaBase { 530 | constructor(properties, cloningFunction) { 531 | const { target, parent, ownKey } = properties; 532 | if (parent && ownKey !== undefined) { 533 | this.parent = parent; 534 | this.ownKey = ownKey; 535 | } else { 536 | this.parent = null; 537 | this.ownKey = null; 538 | } 539 | const targetClone = cloningFunction(target, this); 540 | this.observers = []; 541 | this.revocable = Proxy.revocable(targetClone, this); 542 | this.proxy = this.revocable.proxy; 543 | this.target = targetClone; 544 | this.options = this.processOptions(properties.options); 545 | } 546 | 547 | processOptions(options) { 548 | if (options) { 549 | if (typeof options !== 'object') { 550 | throw new Error(`Observable options if/when provided, MAY only be an object, got '${options}'`); 551 | } 552 | const invalidOptions = Object.keys(options).filter(option => !validObservableOptionKeys.hasOwnProperty(option)); 553 | if (invalidOptions.length) { 554 | throw new Error(`'${invalidOptions.join(', ')}' is/are not a valid Observable option/s`); 555 | } 556 | return Object.assign({}, options); 557 | } else { 558 | return {}; 559 | } 560 | } 561 | 562 | detach() { 563 | this.parent = null; 564 | return this.target; 565 | } 566 | 567 | set(target, key, value) { 568 | let oldValue = target[key]; 569 | 570 | if (value !== oldValue) { 571 | const newValue = getObservedOf(value, key, this); 572 | target[key] = newValue; 573 | 574 | if (oldValue && typeof oldValue === 'object') { 575 | const tmpObserved = oldValue[oMetaKey]; 576 | if (tmpObserved) { 577 | oldValue = tmpObserved.detach(); 578 | } 579 | } 580 | 581 | const changes = oldValue === undefined 582 | ? [{ type: INSERT, path: [key], value: newValue, object: this.proxy }] 583 | : [{ type: UPDATE, path: [key], value: newValue, oldValue: oldValue, object: this.proxy }]; 584 | callObservers(this, changes); 585 | } 586 | 587 | return true; 588 | } 589 | 590 | deleteProperty(target, key) { 591 | let oldValue = target[key]; 592 | 593 | delete target[key]; 594 | 595 | if (oldValue && typeof oldValue === 'object') { 596 | const tmpObserved = oldValue[oMetaKey]; 597 | if (tmpObserved) { 598 | oldValue = tmpObserved.detach(); 599 | } 600 | } 601 | 602 | const changes = [{ type: DELETE, path: [key], oldValue: oldValue, object: this.proxy }]; 603 | callObservers(this, changes); 604 | 605 | return true; 606 | } 607 | } 608 | 609 | class ObjectOMeta extends OMetaBase { 610 | constructor(properties) { 611 | super(properties, prepareObject); 612 | } 613 | } 614 | 615 | class ArrayOMeta extends OMetaBase { 616 | constructor(properties) { 617 | super(properties, prepareArray); 618 | } 619 | 620 | get(target, key) { 621 | if (proxiedArrayMethods.hasOwnProperty(key)) { 622 | return proxiedArrayMethods[key]; 623 | } else { 624 | return target[key]; 625 | } 626 | } 627 | } 628 | 629 | class TypedArrayOMeta extends OMetaBase { 630 | constructor(properties) { 631 | super(properties, prepareTypedArray); 632 | } 633 | 634 | get(target, key) { 635 | if (proxiedTypedArrayMethods.hasOwnProperty(key)) { 636 | return proxiedTypedArrayMethods[key]; 637 | } else { 638 | return target[key]; 639 | } 640 | } 641 | } 642 | 643 | class Observable { 644 | constructor() { 645 | throw new Error('Observable MAY NOT be created via constructor, see "Observable.from" API'); 646 | } 647 | 648 | static from(target, options) { 649 | if (!target || typeof target !== 'object') { 650 | throw new Error('observable MAY ONLY be created from a non-null object'); 651 | } else if (target[oMetaKey]) { 652 | return target; 653 | } else if (Array.isArray(target)) { 654 | return new ArrayOMeta({ target: target, ownKey: null, parent: null, options: options }).proxy; 655 | } else if (ArrayBuffer.isView(target)) { 656 | return new TypedArrayOMeta({ target: target, ownKey: null, parent: null, options: options }).proxy; 657 | } else if (target instanceof Date || target instanceof Error) { 658 | throw new Error(`${target} found to be one of a on-observable types`); 659 | } else { 660 | return new ObjectOMeta({ target: target, ownKey: null, parent: null, options: options }).proxy; 661 | } 662 | } 663 | 664 | static isObservable(input) { 665 | return !!(input && input[oMetaKey]); 666 | } 667 | } 668 | 669 | Object.freeze(Observable); 670 | 671 | export { Observable }; -------------------------------------------------------------------------------- /src/EasybaseProvider/table.ts: -------------------------------------------------------------------------------- 1 | import { 2 | POST_TYPES, 3 | QueryOptions, 4 | Globals 5 | } from "./types"; 6 | import _g from "./g"; 7 | 8 | import authFactory from "./auth"; 9 | 10 | export default function tableFactory(globals?: Globals): any { 11 | 12 | const g = globals || _g; 13 | 14 | const { tokenPost } = authFactory(g); 15 | 16 | const Query = async (options: QueryOptions): Promise[]> => { 17 | const defaultOptions: QueryOptions = { 18 | queryName: "" 19 | } 20 | 21 | const fullOptions: QueryOptions = { ...defaultOptions, ...options }; 22 | 23 | const res = await tokenPost(POST_TYPES.GET_QUERY, fullOptions); 24 | if (res.success) { 25 | g.analyticsEnabled && g.analyticsEventsToTrack.query && g.analyticsEvent('query', { queryName: fullOptions.queryName, tableName: fullOptions.tableName || undefined }); 26 | return res.data 27 | } else { 28 | return []; 29 | } 30 | } 31 | 32 | async function fullTableSize(): Promise; 33 | async function fullTableSize(tableName: string): Promise; 34 | async function fullTableSize(tableName?: string): Promise { 35 | const res = await tokenPost(POST_TYPES.TABLE_SIZE, tableName ? { tableName } : {}); 36 | if (res.success) { 37 | g.analyticsEnabled && g.analyticsEventsToTrack.full_table_size && g.analyticsEvent('full_table_size', { tableName: tableName || undefined }); 38 | return res.data; 39 | } else { 40 | return 0; 41 | } 42 | } 43 | 44 | async function tableTypes(): Promise>; 45 | async function tableTypes(tableName: string): Promise> 46 | async function tableTypes(tableName?: string): Promise> { 47 | const res = await tokenPost(POST_TYPES.COLUMN_TYPES, tableName ? { tableName } : {}); 48 | if (res.success) { 49 | g.analyticsEnabled && g.analyticsEventsToTrack.table_types && g.analyticsEvent('table_types', { tableName: tableName || undefined }); 50 | return res.data; 51 | } else { 52 | return {}; 53 | } 54 | } 55 | 56 | return { 57 | Query, 58 | fullTableSize, 59 | tableTypes 60 | }; 61 | } -------------------------------------------------------------------------------- /src/EasybaseProvider/types.ts: -------------------------------------------------------------------------------- 1 | import { SQW } from "easyqb/types/sq"; 2 | import { NewExpression } from "easyqb/types/expression"; 3 | 4 | export interface ConfigureFrameOptions { 5 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 6 | offset?: number; 7 | /** Limit the amount of records to be retrieved. Set to -1 or null to return all records. Can be used in combination with offset. */ 8 | limit?: number | null; 9 | /** Table to sync frame with. (Projects only) */ 10 | tableName?: string; 11 | } 12 | 13 | export interface GoogleAnalyticsEvents { 14 | login?: boolean; 15 | sign_up?: boolean; 16 | forgot_password?: boolean; 17 | forgot_password_confirm?: boolean; 18 | reset_user_password?: boolean; 19 | get_user_attributes?: boolean; 20 | set_user_attribute?: boolean; 21 | query?: boolean; 22 | full_table_size?: boolean; 23 | table_types?: boolean; 24 | db_one?: boolean; 25 | db_all?: boolean; 26 | } 27 | 28 | export interface EasybaseProviderPropsOptions { 29 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 30 | authentication?: string; 31 | /** Log Easybase react status and events to console. */ 32 | logging?: boolean; 33 | /** Google Analytics 4 Measurement ID for activity reporting */ 34 | googleAnalyticsId?: string; 35 | /** **Only Required for React Native** – Google Analytics 4 Measurement Protocol Secret ID for activity reporting. To create a new secret, navigate in the Google Analytics UI to: Admin > Data Streams > choose your stream > Measurement Protocol > Create */ 36 | googleAnalyticsSecret?: string; 37 | /** 38 | * Specify which extra events are tracked in Google Analytics 39 | * 40 | * **default**: 41 | * * Page Mount 42 | * * login 43 | * * sign_up 44 | * * forgot_password 45 | * * forgot_password_confirm 46 | * * reset_user_password 47 | * 48 | */ 49 | googleAnalyticsEventTracking?: GoogleAnalyticsEvents; 50 | } 51 | 52 | 53 | export interface EasybaseProviderProps { 54 | /** Easybase ebconfig object. Can be downloaded in the integration drawer next to 'React Token'. This is automatically generated. */ 55 | ebconfig: Ebconfig; 56 | /** Optional configuration parameters. */ 57 | options?: EasybaseProviderPropsOptions 58 | } 59 | 60 | export interface FrameConfiguration { 61 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 62 | offset: number; 63 | /** Limit the amount of records to be retrieved. Set to -1 or null to return all records. Can be used in combination with offset. */ 64 | limit: number | null; 65 | /** Table to sync frame with. (Projects only) */ 66 | tableName?: string; 67 | } 68 | 69 | export interface Ebconfig { 70 | tt?: string, 71 | integration: string, 72 | version: string 73 | } 74 | 75 | export interface AddRecordOptions { 76 | /** If true, record will be inserted at the end of the collection rather than the front. Overwrites absoluteIndex. */ 77 | insertAtEnd?: boolean; 78 | /** Values to post to Easybase collection. Format is { column name: value } */ 79 | newRecord: Record; 80 | /** Table to post new record to. (Projects only) */ 81 | tableName?: string; 82 | } 83 | 84 | export interface DeleteRecordOptions { 85 | record: Record; 86 | /** Table to delete record from. (Projects only) */ 87 | tableName?: string; 88 | } 89 | 90 | export interface QueryOptions { 91 | /** Name of the query saved in Easybase's Visual Query Builder */ 92 | queryName: string; 93 | /** If you would like to sort the order of your query by a column. Pass the name of that column here */ 94 | sortBy?: string; 95 | /** By default, columnToSortBy will sort your query by ascending value (1, 2, 3...). To sort by descending set this to true */ 96 | descending?: boolean; 97 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 98 | offset?: number; 99 | /** Limit the amount of records to be retrieved. Can be used in combination with offset. */ 100 | limit?: number; 101 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. Read more: https://easybase.io/about/2020/09/15/Customizing-query-values/ */ 102 | customQuery?: Record; 103 | /** Table to query. (Projects only) */ 104 | tableName?: string; 105 | } 106 | 107 | export interface FileFromURI { 108 | /** Path on local device to the attachment. Usually received from react-native-image-picker or react-native-document-picker */ 109 | uri: string, 110 | /** Name of the file with proper extension */ 111 | name: string, 112 | /** File MIME type */ 113 | type: string 114 | } 115 | 116 | export interface UpdateRecordAttachmentOptions { 117 | /** Easybase Record to attach this attachment to */ 118 | record: Record; 119 | /** The name of the column that is of type file/image/video */ 120 | columnName: string; 121 | /** Either an HTML File element containing the correct type of attachment or a FileFromURI object for React Native instances. 122 | * For React Native use libraries such as react-native-image-picker and react-native-document-picker. 123 | * The file name must have a proper file extension corresponding to the attachment. 124 | */ 125 | attachment: File | FileFromURI; 126 | /** Table to post attachment to. (Projects only) */ 127 | tableName?: string; 128 | } 129 | 130 | export interface StatusResponse { 131 | /** Returns true if the operation was successful */ 132 | success: boolean; 133 | /** Readable description of the the operation's status */ 134 | message: string; 135 | /** Will represent a corresponding error if an error was thrown during the operation. */ 136 | errorCode?: string; 137 | } 138 | 139 | export interface EmailTemplate { 140 | /** Optional header of email that will be sent to user with verification code */ 141 | greeting?: string; 142 | /** Optional name of application for placement within email */ 143 | appName?: string; 144 | /** Optional footer of verification email often used for disclaimers. Can be a valid HTML string */ 145 | footer?: string; 146 | } 147 | 148 | export enum POST_TYPES { 149 | UPLOAD_ATTACHMENT = "upload_attachment", 150 | HANDSHAKE = "handshake", 151 | VALID_TOKEN = "valid_token", 152 | GET_FRAME = "get_frame", 153 | TABLE_SIZE = "table_size", 154 | COLUMN_TYPES = "column_types", 155 | SYNC_STACK = "sync_stack", 156 | SYNC_DELETE = "sync_delete", 157 | SYNC_INSERT = "sync_insert", 158 | GET_QUERY = "get_query", 159 | USER_ATTRIBUTES = "user_attributes", 160 | SET_ATTRIBUTE = "set_attribute", 161 | SIGN_UP = "sign_up", 162 | REQUEST_TOKEN = "request_token", 163 | EASY_QB = "easyqb", 164 | RESET_PASSWORD = "reset_password", 165 | FORGOT_PASSWORD_SEND = "forgot_password_send", 166 | FORGOT_PASSWORD_CONFIRM = "forgot_password_confirm" 167 | } 168 | 169 | export enum DB_STATUS { 170 | ERROR = "error", 171 | PENDING = "pending", 172 | SUCCESS = "success" 173 | } 174 | 175 | export enum EXECUTE_COUNT { 176 | ALL = "all", 177 | ONE = "one" 178 | } 179 | 180 | export interface AuthPostResponse { 181 | success: boolean; 182 | data: any; 183 | } 184 | 185 | export interface ContextValue { 186 | /** 187 | * Signs out the current user. 188 | */ 189 | signOut(): void; 190 | /** 191 | * Retrieve the currently signed in users attribute object. 192 | * @async 193 | * @return {Promise>} Promise> 194 | */ 195 | getUserAttributes(): Promise>; 196 | /** 197 | * Set a single attribute of the currently signed in user. Can also be updated visually in the Easybase 'Users' tab. 198 | * @async 199 | * @param key Object key. Can be a new key or existing key. 200 | * @param value attribute value. 201 | * @return {Promise} Promise 202 | */ 203 | setUserAttribute(key: string, value: string): Promise; 204 | /** 205 | * Reset the currently signed-in user's password to a new string. 206 | * @async 207 | * @param {string} currentPassword Signed-in user's current password 208 | * @param {string} newPassword New password for user's account 209 | * @return {Promise} Promise 210 | */ 211 | resetUserPassword(currentPassword: string, newPassword: string): Promise; 212 | /** 213 | * Sign in a user that already exists for a project. 214 | * @async 215 | * @param userID unique identifier for new user. Usually an email or phone number. 216 | * @param password user password. 217 | * @return {Promise} Promise 218 | */ 219 | signIn(userID: string, password: string): Promise; 220 | /** 221 | * Create a new user for your project. You must still call signIn() after signing up. 222 | * @async 223 | * @param newUserID unique identifier for new user. Usually an email or phone number. 224 | * @param password user password. Must be at least 8 characters long. 225 | * @param userAttributes Optional object to store user attributes. Can also be edited visually in the Easybase 'Users' tab. 226 | * @return {Promise} Promise 227 | */ 228 | signUp(newUserID: string, password: string, userAttributes?: Record): Promise; 229 | /** 230 | * **DEPRECATED**: Use `db` instead - https://easybase.github.io/EasyQB/ 231 | * @deprecated Use `db` instead - https://easybase.github.io/EasyQB/ 232 | * @param {ConfigureFrameOptions} options ConfigureFrameOptions 233 | * @return {StatusResponse} StatusResponse 234 | */ 235 | configureFrame(options: ConfigureFrameOptions): StatusResponse; 236 | /** 237 | * **DEPRECATED**: Use `db().insert()` instead - https://easybase.github.io/EasyQB/docs/insert_queries.html 238 | * @deprecated Use `db().insert()` instead - https://easybase.github.io/EasyQB/docs/insert_queries.html 239 | * @async 240 | * @param {AddRecordOptions} options AddRecordOptions 241 | * @return {Promise} Promise 242 | */ 243 | addRecord(options: AddRecordOptions): Promise; 244 | /** 245 | * **DEPRECATED**: Use `db().delete()` instead - https://easybase.github.io/EasyQB/docs/delete_queries.html 246 | * @deprecated Use `db().delete()` instead - https://easybase.github.io/EasyQB/docs/delete_queries.html 247 | * @async 248 | * @param {Record} record 249 | * @return {Promise} Promise 250 | */ 251 | deleteRecord(options: DeleteRecordOptions): Promise; 252 | /** 253 | * **DEPRECATED**: Use `db` instead - https://easybase.github.io/EasyQB/ 254 | * @deprecated Use `db` instead - https://easybase.github.io/EasyQB/ 255 | * @async 256 | * @return {Promise} Promise 257 | */ 258 | sync(): Promise; 259 | /** 260 | * **DEPRECATED**: Use the `setImage` function instead. 261 | * @deprecated Use the `setImage` function instead. 262 | * @async 263 | * @param {UpdateRecordAttachmentOptions} options UpdateRecordAttachmentOptions 264 | * @return {Promise} Promise 265 | */ 266 | updateRecordImage(options: UpdateRecordAttachmentOptions): Promise; 267 | /** 268 | * **DEPRECATED**: Use the `setVideo` function instead. 269 | * @deprecated Use the `setVideo` function instead. 270 | * @async 271 | * @param {UpdateRecordAttachmentOptions} options UpdateRecordAttachmentOptions 272 | * @return {Promise} Promise 273 | */ 274 | updateRecordVideo(options: UpdateRecordAttachmentOptions): Promise; 275 | /** 276 | * **DEPRECATED**: Use the `setFile` function instead. 277 | * @deprecated Use the `setFile` function instead. 278 | * @async 279 | * @param {UpdateRecordAttachmentOptions} options UpdateRecordAttachmentOptions 280 | * @return {Promise} Promise 281 | */ 282 | updateRecordFile(options: UpdateRecordAttachmentOptions): Promise; 283 | /** 284 | * Upload an image to your backend and attach it to a specific record. columnName must reference a column of type 'image'. 285 | * The file must have a valid image extension (png, jpg, heic, etc). 286 | * @async 287 | * @param {string} recordKey The '_key' of the record to attach this image to. Can be retrieved like: `db().return("_key").where({ title: "The Lion King" }).one()` 288 | * @param {string} columnName The name of the column that is of type image to attach. 289 | * @param {File | FileFromURI} image Either an HTML File element or a FileFromURI object for React Native instances. For React Native, use libraries such as react-native-image-picker and react-native-document-picker. The file name must have a valid image file extension. 290 | * @param {string} [tableName] Table to post attachment to. (Projects only) 291 | * @return {Promise} Promise 292 | */ 293 | setImage(recordKey: string, columnName: string, image: File | FileFromURI, tableName?: string): Promise; 294 | /** 295 | * Upload a video to your backend and attach it to a specific record. columnName must reference a column of type 'video'. 296 | * The file must have a valid video extension (webm, mp4, mov, etc). 297 | * @async 298 | * @param {string} recordKey The '_key' of the record to attach this image to. Can be retrieved like: `db().return("_key").where({ title: "The Lion King" }).one()` 299 | * @param {string} columnName The name of the column that is of type video to attach. 300 | * @param {File | FileFromURI} video Either an HTML File element or a FileFromURI object for React Native instances. For React Native, use libraries such as react-native-image-picker and react-native-document-picker. The file name must have a valid video file extension. 301 | * @param {string} [tableName] Table to post attachment to. (Projects only) 302 | * @return {Promise} Promise 303 | */ 304 | setVideo(recordKey: string, columnName: string, video: File | FileFromURI, tableName?: string): Promise; 305 | /** 306 | * Upload a file to your backend and attach it to a specific record. columnName must reference a column of type 'file'. 307 | * @async 308 | * @param {string} recordKey The '_key' of the record to attach this image to. Can be retrieved like: `db().return("_key").where({ title: "The Lion King" }).one()` 309 | * @param {string} columnName The name of the column that is of type file to attach. 310 | * @param {File | FileFromURI} file Either an HTML File element or a FileFromURI object for React Native instances. For React Native, use libraries such as react-native-image-picker and react-native-document-picker. 311 | * @param {string} [tableName] Table to post attachment to. (Projects only) 312 | * @return {Promise} Promise 313 | */ 314 | setFile(recordKey: string, columnName: string, file: File | FileFromURI, tableName?: string): Promise; 315 | /** 316 | * **DEPRECATED**: Use `db` instead - https://easybase.github.io/EasyQB/ 317 | * @deprecated Use `db` instead - https://easybase.github.io/EasyQB/ 318 | * @return {Record[]} Array of records corresponding to the current frame. Call sync() to push changes that you have made to this array. 319 | */ 320 | Frame(): Record[]; 321 | /** 322 | * **DEPRECATED**: Use `db` instead - https://easybase.github.io/EasyQB/ 323 | * @deprecated Use `db` instead - https://easybase.github.io/EasyQB/ 324 | * @param {number} [index] Passing an index will only return the object at that index in your Frame, rather than the entire array. This is useful for editing single objects based on an index. 325 | * @return {Record} Single record corresponding to that object within the current frame. Call sync() to push changes that you have made to this object. 326 | */ 327 | Frame(index: number): Record; 328 | /** 329 | * Gets the number of records in your table. 330 | * @async 331 | * @returns {Promise} The the number of records in your table. 332 | */ 333 | fullTableSize(): Promise; 334 | /** 335 | * Gets the number of records in your table. 336 | * @async 337 | * @param {string} [tableName] Name of table to get the sizes of. (Projects only) 338 | * @returns {Promise} The the number of records in your table. 339 | */ 340 | fullTableSize(tableName: string): Promise; 341 | /** 342 | * Retrieve an object detailing the columns in your table mapped to their corresponding type. 343 | * @async 344 | * @returns {Promise>} Object detailing the columns in your table mapped to their corresponding type. 345 | */ 346 | tableTypes(): Promise>; 347 | /** 348 | * Retrieve an object detailing the columns in your table mapped to their corresponding type. 349 | * @async 350 | * @param {string} [tableName] Name of table to get the types of. (Projects only) 351 | * @returns {Promise>} Object detailing the columns in your table mapped to their corresponding type. 352 | */ 353 | tableTypes(tableName: string): Promise>; 354 | /** 355 | * View your frames current configuration 356 | * @returns {Record} Object contains the `offset` and `length` of your current frame. 357 | */ 358 | currentConfiguration(): FrameConfiguration; 359 | /** 360 | * @async 361 | * View a query by name. This returns an isolated array that has no effect on your frame or frame configuration. sync() and Frame() have no 362 | * relationship with a Query(). An edited Query cannot be synced with your database, use Frame() for realtime 363 | * database array features. 364 | * @param {QueryOptions} options QueryOptions 365 | * @return {Promise[]>} Isolated array of records in the same form as Frame(). Editing this array has no effect and cannot be synced with your database. Use Frame() for realtime database features. 366 | */ 367 | Query(options: QueryOptions): Promise[]>; 368 | /** 369 | * Instantiate EasyQB instance for dynamic CRUD query building: https://easybase.github.io/EasyQB/ 370 | * @param {string} [tableName] Name of your table. 371 | * @param {boolean} [userAssociatedRecordsOnly] **PROJECTS ONLY** Operations will only be performed on records already associated to the currently signed-in user. Inserted records will automatically be associate to the user. 372 | * @returns {SQW} EasyQB object for dynamic querying: https://easybase.github.io/EasyQB/ 373 | */ 374 | db(tableName?: string, userAssociatedRecordsOnly?: boolean): SQW; 375 | /** 376 | * Subscribe to db events, invoked by calling `.all` or `.one`: https://easybase.github.io/EasyQB/ 377 | * @param {function(status?: DB_STATUS, queryType?: string, executeCount?: EXECUTE_COUNT, tableName?: string | null, returned?: any):void} [callback] Callback function to execute on db operations. 378 | * @returns {function():void} Calling this function unsubscribes your callback function from events. 379 | */ 380 | dbEventListener(callback: (status?: DB_STATUS, queryType?: string, executeCount?: EXECUTE_COUNT, tableName?: string | null, returned?: any) => void): () => void; 381 | /** 382 | * Expressions and operations builder for `.db()`, used to create complex conditions, aggregators, and clauses. https://easybase.github.io/EasyQB/docs/operations.html 383 | */ 384 | e: NewExpression; 385 | /** 386 | * @async 387 | * Trigger an email to the given username with a verification code to reset the user's password. This verification 388 | * code is used in the `forgotPasswordConfirm` function, along with a new password. **The username must be the user's email address**. 389 | * @param {string} username A username which must also be a valid email address 390 | * @param {EmailTemplate} emailTemplate Optional details for the formatting & content of the verification email 391 | * @return {Promise} A StatusResponse corresponding to the successful sending of a verification code email 392 | */ 393 | forgotPassword(username: string, emailTemplate?: EmailTemplate): Promise 394 | /** 395 | * @async 396 | * Confirm the resetting of an unauthenticated users password. This function is invoked after `forgotPassword` is used to trigger 397 | * an email containing a verification code to the given username [*which must also be an email*]. The user's randomly generated 398 | * verification code from their email is passed in the first parameter. 399 | * @param {string} code Verification code found in the email sent from the `forgotPassword` function 400 | * @param {string} username The same username (email) used in the `forgotPassword` function 401 | * @param {string} newPassword The new password for the corresponding verified user 402 | * @return {Promise} A StatusResponse corresponding to the successful setting of a new password 403 | */ 404 | forgotPasswordConfirm(code: string, username: string, newPassword: string): Promise 405 | /** 406 | * Retrieve the currently signed-in user's ID. 407 | * @return {string | undefined} The currently signed-in user's ID (username), otherwise undefined. 408 | */ 409 | userID(): string | undefined; 410 | } 411 | 412 | export interface Globals { 413 | ebconfig: Ebconfig; 414 | token: string; 415 | refreshToken: string; 416 | session: number; 417 | options: EasybaseProviderPropsOptions; 418 | instance: "Node" | "React" | "React Native"; 419 | mounted: boolean; 420 | newTokenCallback(): void; 421 | userID: string | undefined; 422 | analyticsEnabled: boolean; 423 | analyticsEventsToTrack: GoogleAnalyticsEvents; 424 | analyticsEvent(eventTitle: string, params?: Record): void; 425 | analyticsIdentify(hashedUserId: string): void; 426 | GA_USER_ID_SALT: string; // https://support.google.com/analytics/answer/6366371?hl=en#hashed 427 | } 428 | -------------------------------------------------------------------------------- /src/EasybaseProvider/utils.ts: -------------------------------------------------------------------------------- 1 | import _g from "./g"; 2 | import { Globals } from "./types"; 3 | 4 | export default function utilsFactory(globals?: Globals): any { 5 | const g = globals || _g; 6 | 7 | const generateBareUrl = (type: string, integrationID: string): string => `https://api.easybase.io/${type}/${integrationID}`; 8 | 9 | const generateAuthBody = (): any => { 10 | const stamp = Date.now(); 11 | return { 12 | token: g.token, 13 | token_time: ~~(g.session / (stamp % 64)), 14 | now: stamp 15 | } 16 | } 17 | 18 | function log(...params: any): void { 19 | if (g.options.logging) { 20 | console.log("EASYBASE — ", ...params); 21 | } 22 | } 23 | 24 | return { 25 | generateAuthBody, 26 | generateBareUrl, 27 | log 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'cross-fetch'; 2 | 3 | export { default as EasybaseProvider } from "./EasybaseProvider/EasybaseProvider"; 4 | 5 | const generateBareUrl = (type, integrationID) => `https://api.easybase.io/${type}/${integrationID}`; 6 | const isBadInt = (my_int) => my_int !== undefined && my_int !== null && Math.floor(my_int) !== my_int; 7 | const isBadString = (my_string) => my_string !== undefined && my_string !== null && typeof my_string !== "string"; 8 | const isBadIntegrationID = (my_string) => my_string === undefined || my_string === null || typeof my_string !== "string"; 9 | const isBadObject = (my_obj) => my_obj !== undefined && my_obj !== null && typeof my_obj !== "object"; 10 | const isBadBool = (my_bool) => my_bool !== undefined && my_bool !== null && typeof my_bool !== "boolean"; 11 | 12 | interface GetOptions { 13 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 14 | integrationID: string; 15 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 16 | offset?: number; 17 | /** Limit the amount of records to be retrieved. Can be used in combination with offset. */ 18 | limit?: number; 19 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 20 | authentication?: string; 21 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 22 | customQuery?: Record; 23 | } 24 | 25 | /** 26 | * 27 | * @param {GetOptions} options GetOptions. 28 | * @returns {Promise} Array of records. 29 | * 30 | */ 31 | export function get(options: GetOptions): Promise>> { 32 | 33 | const defaultOptions: GetOptions = { 34 | integrationID: "", 35 | offset: undefined, 36 | limit: undefined, 37 | authentication: undefined, 38 | customQuery: undefined 39 | } 40 | const { integrationID, offset, limit, authentication, customQuery } = { ...defaultOptions, ...options }; 41 | 42 | if (isBadIntegrationID(integrationID)) throw new Error("integrationID is required and must be a string"); 43 | if (isBadInt(offset)) throw new Error("offset must be an integer"); 44 | if (isBadInt(limit)) throw new Error("limit must be an integer"); 45 | if (isBadString(authentication)) throw new Error("authentication must be a string or null"); 46 | if (isBadObject(customQuery)) throw new Error("customQuery must be an object or null"); 47 | 48 | return new Promise((resolve, reject) => { 49 | try { 50 | let fetch_body: any = {}; 51 | if (typeof customQuery === "object") fetch_body = { ...customQuery }; 52 | if (offset !== undefined) fetch_body.offset = offset; 53 | if (limit !== undefined) fetch_body.limit = limit; 54 | if (authentication !== undefined) fetch_body.authentication = authentication; 55 | 56 | fetch(generateBareUrl('get', integrationID), { 57 | method: "POST", 58 | body: JSON.stringify(fetch_body), 59 | headers: { 60 | 'Accept': 'application/json', 61 | 'Content-Type': 'application/json' 62 | } 63 | }) 64 | .then(res => res.json()) 65 | .then(resData => { 66 | if ({}.hasOwnProperty.call(resData, 'ErrorCode')) { 67 | console.error(resData.message); 68 | resolve([resData.message]); 69 | } else resolve(resData); 70 | }); 71 | } 72 | catch (err) { reject(err); } 73 | }); 74 | } 75 | 76 | 77 | 78 | interface PostOptions { 79 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 80 | integrationID: string; 81 | /** Values to post to Easybase collection. Format is { column name: value } */ 82 | newRecord: Record; 83 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 84 | authentication?: string; 85 | /** If true, record will be inserted at the end of the collection rather than the front. */ 86 | insertAtEnd?: boolean; 87 | } 88 | 89 | /** 90 | * 91 | * @param {PostOptions} options PostOptions 92 | * @returns {Promise} Post status. 93 | * 94 | */ 95 | export function post(options: PostOptions): Promise { 96 | 97 | const defaultValues: PostOptions = { 98 | integrationID: "", 99 | newRecord: {}, 100 | authentication: undefined, 101 | insertAtEnd: undefined 102 | } 103 | 104 | const { integrationID, newRecord, authentication, insertAtEnd } = { ...defaultValues, ...options }; 105 | 106 | if (isBadIntegrationID(integrationID)) throw new Error("integrationID is required and must be a string"); 107 | if (isBadObject(newRecord)) throw new Error("newRecord is required and must be a string"); 108 | if (isBadString(authentication)) throw new Error("authentication must be a string or null"); 109 | if (isBadBool(insertAtEnd)) throw new Error("insertAtEnd must be a boolean or null"); 110 | 111 | return new Promise((resolve, reject) => { 112 | try { 113 | const fetch_body: any = { ...newRecord }; 114 | if (authentication !== undefined) fetch_body.authentication = authentication; 115 | if (insertAtEnd !== undefined) fetch_body.insertAtEnd = insertAtEnd; 116 | 117 | fetch(generateBareUrl('post', integrationID), { 118 | method: "POST", 119 | body: JSON.stringify(fetch_body), 120 | headers: { 121 | 'Accept': 'application/json', 122 | 'Content-Type': 'application/json' 123 | } 124 | }) 125 | .then(res => res.json()) 126 | .then(resData => { 127 | if ({}.hasOwnProperty.call(resData, 'ErrorCode')) console.error(resData.message); 128 | resolve(resData); 129 | }); 130 | } 131 | catch (err) { reject(err); } 132 | }); 133 | } 134 | 135 | 136 | interface UpdateOptions { 137 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 138 | integrationID: string; 139 | /** Values to update records with. Format is { column_name: new value } */ 140 | updateValues: Record; 141 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 142 | authentication?: string; 143 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 144 | customQuery?: Record; 145 | } 146 | 147 | /** 148 | * 149 | * @param {UpdateOptions} options UpdateOptions 150 | * @returns {Promise} Update status. 151 | */ 152 | export function update(options: UpdateOptions): Promise { 153 | const defaultValues: UpdateOptions = { 154 | integrationID: "", 155 | updateValues: {}, 156 | authentication: undefined, 157 | customQuery: undefined 158 | } 159 | 160 | const { integrationID, updateValues, authentication, customQuery } = { ...defaultValues, ...options }; 161 | 162 | if (isBadIntegrationID(integrationID)) throw new Error("integrationID is required and must be a string"); 163 | if (isBadObject(updateValues) || updateValues === undefined) throw new Error("updateValues is required and must be a string"); 164 | if (isBadString(authentication)) throw new Error("authentication must be a string or null"); 165 | if (isBadObject(customQuery)) throw new Error("customQuery must be an object or null"); 166 | 167 | return new Promise((resolve, reject) => { 168 | try { 169 | const fetch_body: any = { updateValues, ...customQuery }; 170 | if (authentication !== undefined) fetch_body.authentication = authentication; 171 | 172 | fetch(generateBareUrl('update', integrationID), { 173 | method: "POST", 174 | body: JSON.stringify(fetch_body), 175 | headers: { 176 | 'Accept': 'application/json', 177 | 'Content-Type': 'application/json' 178 | } 179 | }) 180 | .then(res => res.json()) 181 | .then(resData => { 182 | if ({}.hasOwnProperty.call(resData, 'ErrorCode')) console.error(resData.message); 183 | resolve(resData.message); 184 | }); 185 | } 186 | catch (err) { reject(err); } 187 | }); 188 | } 189 | 190 | interface DeleteOptions { 191 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 192 | integrationID: string; 193 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 194 | authentication?: string; 195 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 196 | customQuery?: Record; 197 | } 198 | 199 | 200 | /** 201 | * 202 | * @param {DeleteOptions} options DeleteOptions 203 | * @return {Promise} Delete status. 204 | */ 205 | export function Delete(options: DeleteOptions): Promise { 206 | 207 | const defaultValues: DeleteOptions = { 208 | integrationID: "", 209 | authentication: undefined, 210 | customQuery: undefined 211 | } 212 | 213 | const { integrationID, authentication, customQuery } = { ...defaultValues, ...options }; 214 | 215 | if (isBadIntegrationID(integrationID)) throw new Error("integrationID is required and must be a string"); 216 | if (isBadString(authentication)) throw new Error("authentication must be a string or null"); 217 | if (isBadObject(customQuery)) throw new Error("customQuery must be an object or null"); 218 | 219 | return new Promise((resolve, reject) => { 220 | try { 221 | const fetch_body: any = { ...customQuery }; 222 | if (authentication !== undefined) fetch_body.authentication = authentication; 223 | 224 | fetch(generateBareUrl('delete', integrationID), { 225 | method: "POST", 226 | body: JSON.stringify(fetch_body), 227 | headers: { 228 | 'Accept': 'application/json', 229 | 'Content-Type': 'application/json' 230 | } 231 | }) 232 | .then(res => res.json()) 233 | .then(resData => { 234 | if ({}.hasOwnProperty.call(resData, 'ErrorCode')) console.error(resData.message); 235 | resolve(resData.message); 236 | }); 237 | } 238 | catch (err) { reject(err); } 239 | }); 240 | } 241 | 242 | /** 243 | * @async 244 | * Call a cloud function, created in Easybase interface. 245 | * @param {string} route Route as detailed in Easybase. Found under 'Deploy'. Will be in the form of ####...####-function-name. 246 | * @param {Record} postBody Optional object to pass as the body of the POST request. This object will available in your cloud function's event.body. 247 | * @return {Promise} Response from your cloud function. Detailed with a call to 'return context.succeed("RESPONSE")'. 248 | */ 249 | export async function callFunction(route: string, postBody?: Record): Promise { 250 | 251 | const res = await fetch(generateBareUrl('function', route.split("/").pop()), { 252 | method: "POST", 253 | headers: { 254 | 'Content-Type': 'application/json' 255 | }, 256 | body: JSON.stringify(postBody) || "" 257 | }); 258 | 259 | const rawDataText = await res.text(); 260 | return rawDataText; 261 | } -------------------------------------------------------------------------------- /src/restTypes.ts: -------------------------------------------------------------------------------- 1 | export interface GetOptions { 2 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 3 | integrationID: string; 4 | /** Edit starting index from which records will be retrieved from. Useful for paging. */ 5 | offset?: number; 6 | /** Limit the amount of records to be retrieved. Can be used in combination with offset. */ 7 | limit?: number; 8 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 9 | authentication?: string; 10 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 11 | customQuery?: Record; 12 | } 13 | 14 | export interface PostOptions { 15 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 16 | integrationID: string; 17 | /** Values to post to Easybase collection. Format is { column name: value } */ 18 | newRecord: Record; 19 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 20 | authentication?: string; 21 | /** If true, record will be inserted at the end of the collection rather than the front. */ 22 | insertAtEnd?: boolean; 23 | } 24 | 25 | export interface UpdateOptions { 26 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 27 | integrationID: string; 28 | /** Values to update records with. Format is { column_name: new value } */ 29 | updateValues: Record; 30 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 31 | authentication?: string; 32 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 33 | customQuery?: Record; 34 | } 35 | 36 | export interface DeleteOptions { 37 | /** Easybase integration ID. Can be found by expanding the integration menu. This id is automatically generated. */ 38 | integrationID: string; 39 | /** Custom authentication string. Can be set in integration menu. If it is set, it is required to access integration. This acts as an extra layer of security and extensibility. */ 40 | authentication?: string; 41 | /** This object can be set to overwrite the query values as set in the integration menu. If your query is setup to find records where 'age' >= 0, passing in { age: 50 } will query where 'age' >= 50. */ 42 | customQuery?: Record; 43 | } -------------------------------------------------------------------------------- /test/db.js: -------------------------------------------------------------------------------- 1 | import Easybase from "easybasejs"; 2 | import ebconfig from "./ebconfig.js"; 3 | 4 | const NS_PER_SEC = 1e9; 5 | const MS_PER_NS = 1e-6; 6 | const getMsFromHrTime = (diff) => (diff[0] * NS_PER_SEC + diff[1]) * MS_PER_NS; 7 | 8 | async function dbTest() { 9 | const oneTime = process.hrtime(); 10 | const eb = Easybase.EasybaseProvider({ ebconfig }); 11 | const table = eb.db('REACT TEST'); 12 | const x = eb.dbEventListener((status, queryType, c, name) => { 13 | console.log("X: ", status, queryType, c, name); 14 | }) 15 | const { e } = table; 16 | await table.insert({ "app name": 'should be zero', _position: 0 }).one(); 17 | // console.log(await table.insert({ "app name": 'three', _position: 3 }).one()) 18 | // await table.insert({ "app name": 'woo1', _position: 0, rating: 54 }, { "app name": 'woo2', _position: 0 }).one(); 19 | eb.dbEventListener((status, queryType, c, name) => { 20 | console.log("Y: ", status, queryType, c, name) 21 | }) 22 | await table.insert({ "app name": 'should be zero', _position: 0 }).all(); 23 | x() 24 | 25 | // console.log(await table.return().where(e.between('rating', 0, 16)).all()); 26 | console.log(await table.return().limit(10).orderBy({ by: "_position", sort: "desc" }).all()); 27 | console.log(`1 individual request: ${getMsFromHrTime(process.hrtime(oneTime))} MS`); 28 | } 29 | 30 | dbTest(); 31 | 32 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import Easybase from "easybasejs"; 2 | import ebconfig from "./ebconfig.js"; 3 | 4 | const NS_PER_SEC = 1e9; 5 | const MS_PER_NS = 1e-6; 6 | const getMsFromHrTime = (diff) => (diff[0] * NS_PER_SEC + diff[1]) * MS_PER_NS; 7 | 8 | async function speed() { 9 | const oneTime = process.hrtime(); 10 | await Easybase.get({ integrationID: "qF354UrkJ-77Z4Hr", limit: 100 }); 11 | console.log(`1 individual request: ${getMsFromHrTime(process.hrtime(oneTime))} MS`); 12 | 13 | const TEST_LENGTH = 20; 14 | const arrForLength = [...Array(TEST_LENGTH).keys()]; 15 | 16 | const individualTime = process.hrtime(); 17 | for (const _ of arrForLength) { 18 | await Easybase.get({ integrationID: "qF354UrkJ-77Z4Hr", limit: 100 }); 19 | } 20 | console.log(`${TEST_LENGTH} individual request: ${getMsFromHrTime(process.hrtime(individualTime))} MS`); 21 | 22 | const parallelTime = process.hrtime(); 23 | await Promise.all(arrForLength.map(_ => Easybase.get({ integrationID: "qF354UrkJ-77Z4Hr", limit: 100 }))); 24 | console.log(`${TEST_LENGTH} parallel request: ${getMsFromHrTime(process.hrtime(parallelTime))} MS`); 25 | } 26 | 27 | speed(); 28 | -------------------------------------------------------------------------------- /test/node_modules/easybasejs: -------------------------------------------------------------------------------- 1 | ../.. -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "source": "index.js", 7 | "scripts": { 8 | "test:speed": "node ./index.js", 9 | "test:db": "node ./db.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "easybasejs": "file:.." 15 | }, 16 | "files": [ 17 | "src" 18 | ], 19 | "type": "module" 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "allowJs": true, 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | // Ensure that .d.ts files are created by tsc, but not .js files 8 | "declaration": true, 9 | "isolatedModules": true, 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strictNullChecks": true 14 | }, 15 | "include": [ 16 | "./src/**/*" 17 | ], 18 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | entry: './src/index.ts', 4 | output: { 5 | library: 'Easybase', 6 | libraryTarget: 'umd', 7 | filename: 'bundle.js', 8 | }, 9 | devtool: "source-map", 10 | 11 | resolve: { 12 | // Add '.ts' and '.tsx' as resolvable extensions. 13 | extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"] 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'. 19 | { test: /\.tsx?$/, loader: "ts-loader" }, 20 | 21 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 22 | { test: /\.js$/, loader: "source-map-loader" } 23 | ] 24 | } 25 | 26 | } --------------------------------------------------------------------------------