@plantops/web-kit 0.1.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 +88 -0
- package/dist/claims.d.ts +35 -0
- package/dist/claims.d.ts.map +1 -0
- package/dist/claims.js +76 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +52 -0
- package/dist/grants-provider.d.ts +51 -0
- package/dist/grants-provider.d.ts.map +1 -0
- package/dist/grants-provider.js +46 -0
- package/dist/iam-provider.d.ts +95 -0
- package/dist/iam-provider.d.ts.map +1 -0
- package/dist/iam-provider.js +188 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/plantops-provider.d.ts +48 -0
- package/dist/plantops-provider.d.ts.map +1 -0
- package/dist/plantops-provider.js +8 -0
- package/dist/require-auth.d.ts +45 -0
- package/dist/require-auth.d.ts.map +1 -0
- package/dist/require-auth.js +58 -0
- package/dist/scope-coverage.d.ts +54 -0
- package/dist/scope-coverage.d.ts.map +1 -0
- package/dist/scope-coverage.js +41 -0
- package/dist/token-store.d.ts +92 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +144 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/use-async.d.ts +43 -0
- package/dist/use-async.d.ts.map +1 -0
- package/dist/use-async.js +76 -0
- package/dist/use-navigation.d.ts +32 -0
- package/dist/use-navigation.d.ts.map +1 -0
- package/dist/use-navigation.js +24 -0
- package/dist/use-notices.d.ts +47 -0
- package/dist/use-notices.d.ts.map +1 -0
- package/dist/use-notices.js +60 -0
- package/dist/use-permission.d.ts +91 -0
- package/dist/use-permission.d.ts.map +1 -0
- package/dist/use-permission.js +48 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../src/token-store.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAErE,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,KAAK,IAAI,CAAC;AAoCzE;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAItC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAHvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE7B,UAAU,GAAE,MAA0B;IAQnE;;;;;;;;;OASG;IACH,SAAS,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAcnD,gEAAgE;IAChE,OAAO,IAAI,IAAI;IAKf,IAAI,IAAI,YAAY,GAAG,IAAI;IAI3B,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI;IAUxC,WAAW,IAAI,aAAa,GAAG,IAAI;IAQnC,+EAA+E;IAC/E,aAAa,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI;IAMlD,KAAK,IAAI,IAAI;IASb,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,IAAI;CAGb"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/** Default slot. Namespaced so two PlantOps consoles on one host can differ. */
|
|
3
|
+
export const TOKEN_STORAGE_KEY = 'plantops.tokens';
|
|
4
|
+
function safeParse(raw) {
|
|
5
|
+
if (raw === null)
|
|
6
|
+
return null;
|
|
7
|
+
try {
|
|
8
|
+
const parsed = JSON.parse(raw);
|
|
9
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
10
|
+
return null;
|
|
11
|
+
const { tokens, identity } = parsed;
|
|
12
|
+
if (typeof tokens !== 'object' ||
|
|
13
|
+
tokens === null ||
|
|
14
|
+
typeof tokens.accessToken !== 'string') {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
tokens: {
|
|
19
|
+
accessToken: tokens.accessToken,
|
|
20
|
+
refreshToken: typeof tokens.refreshToken === 'string' ? tokens.refreshToken : null,
|
|
21
|
+
expiresAt: typeof tokens.expiresAt === 'number' ? tokens.expiresAt : null,
|
|
22
|
+
},
|
|
23
|
+
identity: identity != null &&
|
|
24
|
+
typeof identity.email === 'string' &&
|
|
25
|
+
typeof identity.clientSlug === 'string'
|
|
26
|
+
? { email: identity.email, clientSlug: identity.clientSlug }
|
|
27
|
+
: null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// A hand-edited or half-written entry is treated as "signed out" rather
|
|
32
|
+
// than crashing the shell. The user signs in again; nothing is lost that
|
|
33
|
+
// was not already unusable.
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A `TokenStore` backed by `localStorage`, with change notification.
|
|
39
|
+
*
|
|
40
|
+
* Safe to construct during server rendering: every method tolerates the absence
|
|
41
|
+
* of `window`, answering "no session", which is the correct answer on a server
|
|
42
|
+
* that has no user.
|
|
43
|
+
*/
|
|
44
|
+
export class BrowserTokenStore {
|
|
45
|
+
storageKey;
|
|
46
|
+
listeners = new Set();
|
|
47
|
+
onStorage;
|
|
48
|
+
constructor(storageKey = TOKEN_STORAGE_KEY) {
|
|
49
|
+
this.storageKey = storageKey;
|
|
50
|
+
this.onStorage = (event) => {
|
|
51
|
+
if (event.storageArea !== this.storage())
|
|
52
|
+
return;
|
|
53
|
+
if (event.key !== null && event.key !== this.storageKey)
|
|
54
|
+
return;
|
|
55
|
+
this.emit(this.readSession());
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Notified on every change, in this tab and in others.
|
|
60
|
+
*
|
|
61
|
+
* The `storage` listener is attached with the first subscriber and detached
|
|
62
|
+
* with the last, rather than in the constructor. That is what makes the store
|
|
63
|
+
* survive React's development double-mount: a listener attached at
|
|
64
|
+
* construction and removed on unmount is gone for good after the first
|
|
65
|
+
* remount — cross-tab sign-out then silently stops working, in development
|
|
66
|
+
* only, which is the worst place for it to hide.
|
|
67
|
+
*/
|
|
68
|
+
subscribe(listener) {
|
|
69
|
+
if (this.listeners.size === 0) {
|
|
70
|
+
globalThis.addEventListener?.('storage', this.onStorage);
|
|
71
|
+
}
|
|
72
|
+
this.listeners.add(listener);
|
|
73
|
+
return () => {
|
|
74
|
+
this.listeners.delete(listener);
|
|
75
|
+
if (this.listeners.size === 0) {
|
|
76
|
+
globalThis.removeEventListener?.('storage', this.onStorage);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Drops every subscriber and the window listener with them. */
|
|
81
|
+
dispose() {
|
|
82
|
+
this.listeners.clear();
|
|
83
|
+
globalThis.removeEventListener?.('storage', this.onStorage);
|
|
84
|
+
}
|
|
85
|
+
read() {
|
|
86
|
+
return this.readSession()?.tokens ?? null;
|
|
87
|
+
}
|
|
88
|
+
write(tokens) {
|
|
89
|
+
if (tokens === null) {
|
|
90
|
+
this.clear();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// A refresh rotates the tokens and knows nothing about the identity hint,
|
|
94
|
+
// so the hint is carried across rather than dropped on every renewal.
|
|
95
|
+
this.writeSession({ tokens, identity: this.readSession()?.identity ?? null });
|
|
96
|
+
}
|
|
97
|
+
readSession() {
|
|
98
|
+
try {
|
|
99
|
+
return safeParse(this.storage()?.getItem(this.storageKey) ?? null);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Records who signed in, for the header. Never used to authorise anything. */
|
|
106
|
+
writeIdentity(identity) {
|
|
107
|
+
const current = this.readSession();
|
|
108
|
+
if (current === null)
|
|
109
|
+
return;
|
|
110
|
+
this.writeSession({ ...current, identity });
|
|
111
|
+
}
|
|
112
|
+
clear() {
|
|
113
|
+
try {
|
|
114
|
+
this.storage()?.removeItem(this.storageKey);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
/* storage unavailable — nothing was written to remove */
|
|
118
|
+
}
|
|
119
|
+
this.emit(null);
|
|
120
|
+
}
|
|
121
|
+
writeSession(session) {
|
|
122
|
+
try {
|
|
123
|
+
this.storage()?.setItem(this.storageKey, JSON.stringify(session));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Quota, private mode, or a policy that blocks storage. The session still
|
|
127
|
+
// works for this page's lifetime through the client's in-memory copy; it
|
|
128
|
+
// simply will not survive a reload.
|
|
129
|
+
}
|
|
130
|
+
this.emit(session);
|
|
131
|
+
}
|
|
132
|
+
storage() {
|
|
133
|
+
try {
|
|
134
|
+
return globalThis.localStorage ?? null;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
emit(session) {
|
|
141
|
+
for (const listener of this.listeners)
|
|
142
|
+
listener(session);
|
|
143
|
+
}
|
|
144
|
+
}
|