@awesomate/sdk 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,6 +146,11 @@ and takes a fresh copy when it does. Up to 200 rows a list and 20 lists a connec
|
|
|
146
146
|
`{ onRows, onError }` to hear about a list the server refused (a column that does not exist).
|
|
147
147
|
Outside a browser, give it a WebSocket: `createAppClient({ publishableKey, WebSocket })`.
|
|
148
148
|
|
|
149
|
+
While a list is open in a browser, the connection also tells the hub whether the tab is in front.
|
|
150
|
+
If the account switched on reply emails, someone looking at the app isn't emailed about a reply,
|
|
151
|
+
but a tab left open in the background doesn't count as looking, and closing the last one counts as
|
|
152
|
+
leaving straight away.
|
|
153
|
+
|
|
149
154
|
### Staff find a customer
|
|
150
155
|
|
|
151
156
|
People in an app read no contacts but their own, so by default staff cannot put a job under a
|
|
@@ -160,6 +165,22 @@ await app.write('job', { title: 'Gutters' }, { links: { customer: jo.id } });
|
|
|
160
165
|
|
|
161
166
|
Name, email and phone only, and nothing the owner marked sensitive. Anyone else is refused.
|
|
162
167
|
|
|
168
|
+
An attribute can also be visible to some roles only (`awesomate_crm_kinds`, `set_visibility`, for
|
|
169
|
+
example a cost only `staff` see). For everyone else it comes back `null` and a write that sets it
|
|
170
|
+
is refused, whatever the app asks for.
|
|
171
|
+
|
|
172
|
+
Some things a customer should do without being able to edit the record: accept their quote, say.
|
|
173
|
+
Save it as a write recipe and open it to their role (`awesomate_crm_recipes`, `run_by`, Pro and
|
|
174
|
+
above), then:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
await app.call('accept_quote', { job: job.id });
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The database lets them do exactly the recipe's steps: only its params take their values, every
|
|
181
|
+
other value is fixed, and an existing record must be one they can already read. A recipe not
|
|
182
|
+
opened to their role is "not found".
|
|
183
|
+
|
|
163
184
|
The rules are kept in your own database, which applies them to every read and write, so a mistake
|
|
164
185
|
in the app cannot show anyone more than their role allows. A disabled person is refused on their
|
|
165
186
|
next call. Sessions are kept in `localStorage`; in a Capacitor app pass `storage` (Preferences) and
|
package/dist/index.d.ts
CHANGED
|
@@ -492,6 +492,13 @@ export declare class AwesomateAppClient {
|
|
|
492
492
|
/** Create a record (returns its id) or change one with options.id, inside the kind's write rule for this user. */
|
|
493
493
|
write<K extends KindName>(kind: K, data: Partial<RowOf<K>> | Record<string, unknown>, options?: WriteOptions): Promise<string>;
|
|
494
494
|
archive<K extends KindName>(kind: K, id: string): Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Run a recipe the account opened to this person's role: "accept this quote", something their
|
|
497
|
+
* write rule would not let them do by hand. Every step commits or none does; a recipe they may
|
|
498
|
+
* not run reads as not found. Never retried once sent: a write that may have landed is not safe
|
|
499
|
+
* to send twice (a refused sign-in is, and is retried after a refresh like every data call).
|
|
500
|
+
*/
|
|
501
|
+
call<R extends RecipeName>(recipe: R, args?: ArgsOf<R>): Promise<RecipeResult>;
|
|
495
502
|
/**
|
|
496
503
|
* Customers by name or email (two characters or more), or by id, so staff can find one and put
|
|
497
504
|
* a record under them. Only for a role the account lets look customers up in this app; anyone
|
package/dist/index.js
CHANGED
|
@@ -514,6 +514,15 @@ export class AwesomateAppClient {
|
|
|
514
514
|
async archive(kind, id) {
|
|
515
515
|
await this.data('POST', '/call', { fn: 'archive_record', args: { kind, id } });
|
|
516
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Run a recipe the account opened to this person's role: "accept this quote", something their
|
|
519
|
+
* write rule would not let them do by hand. Every step commits or none does; a recipe they may
|
|
520
|
+
* not run reads as not found. Never retried once sent: a write that may have landed is not safe
|
|
521
|
+
* to send twice (a refused sign-in is, and is retried after a refresh like every data call).
|
|
522
|
+
*/
|
|
523
|
+
async call(recipe, args = {}) {
|
|
524
|
+
return (await this.data('POST', '/call', { fn: recipe, args })).result;
|
|
525
|
+
}
|
|
517
526
|
/**
|
|
518
527
|
* Customers by name or email (two characters or more), or by id, so staff can find one and put
|
|
519
528
|
* a record under them. Only for a role the account lets look customers up in this app; anyone
|
|
@@ -544,6 +553,10 @@ function unref(t) {
|
|
|
544
553
|
t.unref?.();
|
|
545
554
|
return t;
|
|
546
555
|
}
|
|
556
|
+
/** The page's document, when there is one: the part live() reads to say whether the tab is in front. */
|
|
557
|
+
function pageDocument() {
|
|
558
|
+
return globalThis.document;
|
|
559
|
+
}
|
|
547
560
|
/** The one WebSocket behind live(). */
|
|
548
561
|
class LiveSocket {
|
|
549
562
|
o;
|
|
@@ -560,6 +573,15 @@ class LiveSocket {
|
|
|
560
573
|
connecting = false;
|
|
561
574
|
constructor(o) {
|
|
562
575
|
this.o = o;
|
|
576
|
+
// Reply emails wait only while one of the person's tabs is in front, so the hub hears when this
|
|
577
|
+
// one goes to the back or comes forward. Outside a browser there is no tab, and nothing is sent.
|
|
578
|
+
pageDocument()?.addEventListener?.('visibilitychange', () => this.sendPresence());
|
|
579
|
+
}
|
|
580
|
+
/** Whether this tab is in front, to the hub (from 0.10.0; an older hub ignores the frame). */
|
|
581
|
+
sendPresence() {
|
|
582
|
+
const d = pageDocument();
|
|
583
|
+
if (this.ready && typeof d?.visibilityState === 'string')
|
|
584
|
+
this.send({ type: 'presence', visible: d.visibilityState === 'visible' });
|
|
563
585
|
}
|
|
564
586
|
add(spec, handlers) {
|
|
565
587
|
const id = `l${++this.seq}`;
|
|
@@ -623,6 +645,7 @@ class LiveSocket {
|
|
|
623
645
|
this.attempt = 0;
|
|
624
646
|
for (const [id, e] of this.entries)
|
|
625
647
|
this.send({ type: 'sub', id, spec: e.spec });
|
|
648
|
+
this.sendPresence();
|
|
626
649
|
this.pinger = unref(setInterval(() => this.send({ type: 'ping' }), 25_000));
|
|
627
650
|
return;
|
|
628
651
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Your own Awesomate data from Node and the browser: query contacts and app data with generated types, and sign your app's own users in",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|