├── .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 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
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 | }
--------------------------------------------------------------------------------