@ilivemylife/react-sdk 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 +37 -0
- package/dist/index.cjs +669 -0
- package/dist/index.d.cts +205 -0
- package/dist/index.d.ts +205 -0
- package/dist/index.js +631 -0
- package/dist/styles.css +44 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @ilivemylife/react-sdk
|
|
2
|
+
|
|
3
|
+
Bring **[iLiveMyLife](https://iLiveMyLife.io)** into your web app: let people **sign in with their
|
|
4
|
+
iLiveMyLife account** and **chat inside your product** — one small React client.
|
|
5
|
+
|
|
6
|
+
## What is iLiveMyLife?
|
|
7
|
+
|
|
8
|
+
[iLiveMyLife](https://iLiveMyLife.io) is a collaborative **knowledge graph**: your information as
|
|
9
|
+
nodes you own, with real-time sync, sharing, an AI assistant (Lifebot), and an open API. One account
|
|
10
|
+
travels across every app built on the platform — and the data stays with its owner.
|
|
11
|
+
|
|
12
|
+
This package is the **React client** that brings two of those capabilities into *your* app:
|
|
13
|
+
|
|
14
|
+
- **Single sign-on** — people log in with their iLiveMyLife account; no passwords for you to store.
|
|
15
|
+
- **In-app messenger** — a real chat inside your product, backed by the graph.
|
|
16
|
+
|
|
17
|
+
## Become a partner
|
|
18
|
+
|
|
19
|
+
We're opening the platform to partner sites. Want iLiveMyLife login and messaging inside your app?
|
|
20
|
+
We onboard partners individually — **[get in touch on DigitalTwins.team](https://digitaltwins.team/in/ilya-sorokin)** and we'll set you up.
|
|
21
|
+
|
|
22
|
+
## Part of the iLiveMyLife platform
|
|
23
|
+
|
|
24
|
+
- **[iLiveMyLife.io](https://iLiveMyLife.io)** · **[app.iLiveMyLife.io](https://app.iLiveMyLife.io)** — the platform and the web app.
|
|
25
|
+
- **[`@ilivemylife/graph-sdk`](https://www.npmjs.com/package/@ilivemylife/graph-sdk)** — read and write the graph from code, the terminal, or an AI assistant (SDK + CLI + MCP server).
|
|
26
|
+
- Built on iLiveMyLife: **[DigitalTwins.team](https://digitaltwins.team)** · **[BasicNeeds.me](https://basicneeds.me)**.
|
|
27
|
+
|
|
28
|
+
## Author
|
|
29
|
+
|
|
30
|
+
Built by **Ilya Sorokin** — founder of iLiveMyLife and [DigitalTwins.team](https://digitaltwins.team).
|
|
31
|
+
|
|
32
|
+
**Profile & message me:** [digitaltwins.team/in/ilya-sorokin](https://digitaltwins.team/in/ilya-sorokin) — contact, links (LinkedIn included), and a direct chat, running on this very SDK.
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
© 2026 Ilya Sorokin. All rights reserved. Proprietary software, published for use with the
|
|
37
|
+
iLiveMyLife platform. For licensing or partnership inquiries, contact the author.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
IlmlButton: () => IlmlButton,
|
|
24
|
+
IlmlChat: () => IlmlChat,
|
|
25
|
+
IlmlChatWidget: () => IlmlChatWidget,
|
|
26
|
+
getIlmlToken: () => getIlmlToken,
|
|
27
|
+
getSsoLoginUrl: () => getSsoLoginUrl,
|
|
28
|
+
isValidSsoOrigin: () => isValidSsoOrigin,
|
|
29
|
+
popupCenterParams: () => popupCenterParams,
|
|
30
|
+
signOutIlmlSession: () => signOutIlmlSession,
|
|
31
|
+
useIlmlProfile: () => useIlmlProfile,
|
|
32
|
+
useIlmlSession: () => useIlmlSession,
|
|
33
|
+
useIlmlSessionState: () => useIlmlSessionState,
|
|
34
|
+
useIlmlSso: () => useIlmlSso
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/ssoHelpers.ts
|
|
39
|
+
function getSsoLoginUrl(ssoUrl, options = {}) {
|
|
40
|
+
const { addTimestamp = true, route = "", clientId } = options;
|
|
41
|
+
const baseUrl = route ? `${ssoUrl}/${route}` : ssoUrl;
|
|
42
|
+
const params = new URLSearchParams();
|
|
43
|
+
if (addTimestamp) {
|
|
44
|
+
params.append("timestamp", Date.now().toString());
|
|
45
|
+
}
|
|
46
|
+
if (clientId) {
|
|
47
|
+
params.append("client_id", clientId);
|
|
48
|
+
}
|
|
49
|
+
const queryString = params.toString();
|
|
50
|
+
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
51
|
+
}
|
|
52
|
+
function popupCenterParams(width, height) {
|
|
53
|
+
return {
|
|
54
|
+
left: window.screen.width / 2 - width / 2,
|
|
55
|
+
top: window.screen.height / 2 - height / 2
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function isValidSsoOrigin(event, ssoUrl) {
|
|
59
|
+
try {
|
|
60
|
+
const expectedOrigin = new URL(ssoUrl).origin;
|
|
61
|
+
return event.origin === expectedOrigin;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/useIlmlSso.ts
|
|
68
|
+
var import_react = require("react");
|
|
69
|
+
var MESSENGER_TYPES = ["messengerReady", "messengerInitialized"];
|
|
70
|
+
function useIlmlSso(opts) {
|
|
71
|
+
const {
|
|
72
|
+
ssoUrl,
|
|
73
|
+
ssoRoute,
|
|
74
|
+
clientId,
|
|
75
|
+
onSsoData,
|
|
76
|
+
onError,
|
|
77
|
+
onClose,
|
|
78
|
+
responseType = "loginSuccess",
|
|
79
|
+
windowName = "ilmlSsoLogin",
|
|
80
|
+
width = 500,
|
|
81
|
+
height = 700
|
|
82
|
+
} = opts;
|
|
83
|
+
const loginWindowRef = (0, import_react.useRef)(null);
|
|
84
|
+
const processingRef = (0, import_react.useRef)(false);
|
|
85
|
+
const openLogin = (0, import_react.useCallback)(() => {
|
|
86
|
+
if (loginWindowRef.current && !loginWindowRef.current.closed) {
|
|
87
|
+
loginWindowRef.current.focus();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
processingRef.current = false;
|
|
91
|
+
const url = getSsoLoginUrl(ssoUrl, { route: ssoRoute, clientId });
|
|
92
|
+
const pos = popupCenterParams(width, height);
|
|
93
|
+
const popup = window.open(
|
|
94
|
+
url,
|
|
95
|
+
windowName,
|
|
96
|
+
`width=${width},height=${height},toolbar=no,location=no,directories=no,status=no,menubar=no,copyhistory=no,top=${pos.top},left=${pos.left}`
|
|
97
|
+
);
|
|
98
|
+
loginWindowRef.current = popup;
|
|
99
|
+
if (!onClose) return;
|
|
100
|
+
if (!popup) {
|
|
101
|
+
onClose();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const poll = window.setInterval(() => {
|
|
105
|
+
if (popup.closed) {
|
|
106
|
+
window.clearInterval(poll);
|
|
107
|
+
if (!processingRef.current) onClose();
|
|
108
|
+
}
|
|
109
|
+
}, 300);
|
|
110
|
+
}, [ssoUrl, ssoRoute, clientId, windowName, width, height, onClose]);
|
|
111
|
+
const onMessage = (0, import_react.useCallback)(
|
|
112
|
+
async (event) => {
|
|
113
|
+
if (!isValidSsoOrigin(event, ssoUrl)) return;
|
|
114
|
+
if (MESSENGER_TYPES.includes(event.data?.type)) return;
|
|
115
|
+
let parsed;
|
|
116
|
+
try {
|
|
117
|
+
parsed = JSON.parse(event.data?.type);
|
|
118
|
+
} catch {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (parsed?.name === "loginError") {
|
|
122
|
+
onError?.("loginError");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (parsed?.name === "signupError") {
|
|
126
|
+
onError?.("signupError");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (parsed?.name === responseType) {
|
|
130
|
+
if (processingRef.current) return;
|
|
131
|
+
processingRef.current = true;
|
|
132
|
+
const data = event.data?.data;
|
|
133
|
+
if (data?.accessToken) {
|
|
134
|
+
try {
|
|
135
|
+
localStorage.setItem("walletAccessToken", data.accessToken);
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (loginWindowRef.current && !loginWindowRef.current.closed) {
|
|
140
|
+
loginWindowRef.current.close();
|
|
141
|
+
}
|
|
142
|
+
await onSsoData(data ?? {});
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
[ssoUrl, responseType, onSsoData, onError]
|
|
146
|
+
);
|
|
147
|
+
(0, import_react.useEffect)(() => {
|
|
148
|
+
window.addEventListener("message", onMessage);
|
|
149
|
+
return () => window.removeEventListener("message", onMessage);
|
|
150
|
+
}, [onMessage]);
|
|
151
|
+
return { openLogin };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/IlmlChat.tsx
|
|
155
|
+
var import_react2 = require("react");
|
|
156
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
157
|
+
function IlmlChat({ ssoUrl, nodeId, token, isNodeReady, className, style }) {
|
|
158
|
+
const iframeRef = (0, import_react2.useRef)(null);
|
|
159
|
+
const mountedRef = (0, import_react2.useRef)(true);
|
|
160
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
161
|
+
(0, import_react2.useEffect)(() => {
|
|
162
|
+
mountedRef.current = true;
|
|
163
|
+
return () => {
|
|
164
|
+
mountedRef.current = false;
|
|
165
|
+
};
|
|
166
|
+
}, []);
|
|
167
|
+
(0, import_react2.useEffect)(() => {
|
|
168
|
+
setLoading(true);
|
|
169
|
+
}, [nodeId]);
|
|
170
|
+
const expectedOrigin = (() => {
|
|
171
|
+
try {
|
|
172
|
+
return new URL(ssoUrl).origin;
|
|
173
|
+
} catch {
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
177
|
+
const resolveToken = (0, import_react2.useCallback)(() => {
|
|
178
|
+
if (token != null) return token;
|
|
179
|
+
try {
|
|
180
|
+
return localStorage.getItem("walletAccessToken");
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}, [token]);
|
|
185
|
+
const handshake = (0, import_react2.useCallback)(async () => {
|
|
186
|
+
if (isNodeReady) {
|
|
187
|
+
for (let attempt = 0; attempt < 15; attempt++) {
|
|
188
|
+
if (!mountedRef.current) return;
|
|
189
|
+
if (await isNodeReady(nodeId)) break;
|
|
190
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const win = iframeRef.current?.contentWindow;
|
|
194
|
+
if (!mountedRef.current || !win) return;
|
|
195
|
+
win.postMessage({ type: "initMessenger", token: resolveToken() }, expectedOrigin);
|
|
196
|
+
setLoading(false);
|
|
197
|
+
}, [isNodeReady, nodeId, resolveToken, expectedOrigin]);
|
|
198
|
+
const handleMessage = (0, import_react2.useCallback)(
|
|
199
|
+
(event) => {
|
|
200
|
+
if (!expectedOrigin || event.origin !== expectedOrigin) return;
|
|
201
|
+
const { type } = event.data || {};
|
|
202
|
+
if (type === "messengerReady") void handshake();
|
|
203
|
+
else if (type === "messengerInitialized") setLoading(false);
|
|
204
|
+
},
|
|
205
|
+
[expectedOrigin, handshake]
|
|
206
|
+
);
|
|
207
|
+
(0, import_react2.useEffect)(() => {
|
|
208
|
+
window.addEventListener("message", handleMessage);
|
|
209
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
210
|
+
}, [handleMessage]);
|
|
211
|
+
const iframeUrl = `${ssoUrl}/embed/app?node=${encodeURIComponent(nodeId)}`;
|
|
212
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: { position: "relative", ...style }, children: [
|
|
213
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
214
|
+
"iframe",
|
|
215
|
+
{
|
|
216
|
+
ref: iframeRef,
|
|
217
|
+
src: iframeUrl,
|
|
218
|
+
title: "iLiveMyLife Chat",
|
|
219
|
+
style: { width: "100%", height: "100%", border: "none" },
|
|
220
|
+
allow: "microphone; camera",
|
|
221
|
+
sandbox: "allow-scripts allow-same-origin allow-forms allow-popups"
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
225
|
+
"div",
|
|
226
|
+
{
|
|
227
|
+
style: {
|
|
228
|
+
position: "absolute",
|
|
229
|
+
inset: 0,
|
|
230
|
+
display: "flex",
|
|
231
|
+
flexDirection: "column",
|
|
232
|
+
alignItems: "center",
|
|
233
|
+
justifyContent: "center",
|
|
234
|
+
gap: 10,
|
|
235
|
+
background: "#fff",
|
|
236
|
+
color: "#64748b",
|
|
237
|
+
fontSize: 13
|
|
238
|
+
},
|
|
239
|
+
children: [
|
|
240
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: "@keyframes ilml-chat-spin{to{transform:rotate(360deg)}}" }),
|
|
241
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
242
|
+
"span",
|
|
243
|
+
{
|
|
244
|
+
style: {
|
|
245
|
+
width: 22,
|
|
246
|
+
height: 22,
|
|
247
|
+
border: "2px solid #e2e8f0",
|
|
248
|
+
borderTopColor: "#0178d4",
|
|
249
|
+
borderRadius: "50%",
|
|
250
|
+
animation: "ilml-chat-spin 0.8s linear infinite"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
),
|
|
254
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Loading chat\u2026" })
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
) : null
|
|
258
|
+
] });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/IlmlChatWidget.tsx
|
|
262
|
+
var import_react3 = require("react");
|
|
263
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
264
|
+
var iconBtn = {
|
|
265
|
+
background: "none",
|
|
266
|
+
border: "none",
|
|
267
|
+
cursor: "pointer",
|
|
268
|
+
color: "rgba(255,255,255,0.82)",
|
|
269
|
+
fontSize: 14,
|
|
270
|
+
lineHeight: 1,
|
|
271
|
+
padding: "2px 6px",
|
|
272
|
+
borderRadius: 4,
|
|
273
|
+
textDecoration: "none",
|
|
274
|
+
display: "inline-flex",
|
|
275
|
+
alignItems: "center"
|
|
276
|
+
};
|
|
277
|
+
function IlmlChatWidget({
|
|
278
|
+
ssoUrl,
|
|
279
|
+
nodeId,
|
|
280
|
+
token,
|
|
281
|
+
title,
|
|
282
|
+
onClose,
|
|
283
|
+
onMinimize,
|
|
284
|
+
isNodeReady,
|
|
285
|
+
allowFullscreen = true,
|
|
286
|
+
allowTall = true,
|
|
287
|
+
accentColor = "#0178d4",
|
|
288
|
+
className,
|
|
289
|
+
style
|
|
290
|
+
}) {
|
|
291
|
+
const [expanded, setExpanded] = (0, import_react3.useState)(false);
|
|
292
|
+
const [tall, setTall] = (0, import_react3.useState)(false);
|
|
293
|
+
const openInAppUrl = `${ssoUrl}/item/${encodeURIComponent(nodeId)}`;
|
|
294
|
+
const dockStyle = {
|
|
295
|
+
display: "flex",
|
|
296
|
+
flexDirection: "column",
|
|
297
|
+
overflow: "hidden",
|
|
298
|
+
height: "70vh",
|
|
299
|
+
maxHeight: 520,
|
|
300
|
+
width: "92vw",
|
|
301
|
+
maxWidth: 360,
|
|
302
|
+
borderRadius: 16,
|
|
303
|
+
background: "#fff",
|
|
304
|
+
boxShadow: "0 20px 50px rgba(0,0,0,0.25)",
|
|
305
|
+
...style,
|
|
306
|
+
// Tall mode wins over any consumer height in `style`: fill (almost) the whole viewport height,
|
|
307
|
+
// leaving a small margin, at the docked width.
|
|
308
|
+
...tall ? { height: "94vh", maxHeight: "94vh" } : {}
|
|
309
|
+
};
|
|
310
|
+
const fullStyle = {
|
|
311
|
+
display: "flex",
|
|
312
|
+
flexDirection: "column",
|
|
313
|
+
overflow: "hidden",
|
|
314
|
+
position: "fixed",
|
|
315
|
+
inset: 0,
|
|
316
|
+
zIndex: 1400,
|
|
317
|
+
background: "#fff"
|
|
318
|
+
};
|
|
319
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: expanded ? void 0 : className, style: expanded ? fullStyle : dockStyle, children: [
|
|
320
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
321
|
+
"div",
|
|
322
|
+
{
|
|
323
|
+
style: {
|
|
324
|
+
display: "flex",
|
|
325
|
+
alignItems: "center",
|
|
326
|
+
gap: 4,
|
|
327
|
+
padding: "8px 12px",
|
|
328
|
+
background: accentColor,
|
|
329
|
+
color: "#fff",
|
|
330
|
+
flexShrink: 0
|
|
331
|
+
},
|
|
332
|
+
children: [
|
|
333
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
334
|
+
"button",
|
|
335
|
+
{
|
|
336
|
+
onClick: onMinimize,
|
|
337
|
+
disabled: !onMinimize,
|
|
338
|
+
title: onMinimize ? "Minimize" : void 0,
|
|
339
|
+
style: {
|
|
340
|
+
...iconBtn,
|
|
341
|
+
flex: 1,
|
|
342
|
+
minWidth: 0,
|
|
343
|
+
textAlign: "left",
|
|
344
|
+
color: "#fff",
|
|
345
|
+
fontWeight: 600,
|
|
346
|
+
fontSize: 14,
|
|
347
|
+
overflow: "hidden",
|
|
348
|
+
textOverflow: "ellipsis",
|
|
349
|
+
whiteSpace: "nowrap",
|
|
350
|
+
display: "block",
|
|
351
|
+
cursor: onMinimize ? "pointer" : "default"
|
|
352
|
+
},
|
|
353
|
+
children: title || "Chat"
|
|
354
|
+
}
|
|
355
|
+
),
|
|
356
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: openInAppUrl, target: "_blank", rel: "noopener", title: "Open in iLiveMyLife", style: iconBtn, children: "\u2197" }),
|
|
357
|
+
allowTall && !expanded ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setTall((v) => !v), title: tall ? "Restore height" : "Full height", style: iconBtn, children: "\u2195" }) : null,
|
|
358
|
+
allowFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setExpanded((v) => !v), title: expanded ? "Restore" : "Full screen", style: iconBtn, children: expanded ? "\u2750" : "\u26F6" }) : null,
|
|
359
|
+
onMinimize ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: onMinimize, title: "Minimize", style: iconBtn, children: "\u2014" }) : null,
|
|
360
|
+
onClose ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: onClose, title: "Close", style: { ...iconBtn, fontSize: 18 }, children: "\xD7" }) : null
|
|
361
|
+
]
|
|
362
|
+
}
|
|
363
|
+
),
|
|
364
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IlmlChat, { ssoUrl, nodeId, token, isNodeReady, style: { flex: 1, minHeight: 0 } })
|
|
365
|
+
] });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/IlmlButton.tsx
|
|
369
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
370
|
+
function IlmlButton({
|
|
371
|
+
href,
|
|
372
|
+
onClick,
|
|
373
|
+
disabled,
|
|
374
|
+
target,
|
|
375
|
+
rel,
|
|
376
|
+
type = "button",
|
|
377
|
+
className,
|
|
378
|
+
style,
|
|
379
|
+
children
|
|
380
|
+
}) {
|
|
381
|
+
const cls = `ilml-btn${className ? ` ${className}` : ""}`;
|
|
382
|
+
if (href) {
|
|
383
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href, onClick, target, rel, className: cls, style, "aria-disabled": disabled || void 0, children });
|
|
384
|
+
}
|
|
385
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type, onClick, disabled, className: cls, style, children });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/useIlmlProfile.ts
|
|
389
|
+
var import_react4 = require("react");
|
|
390
|
+
var DEFAULT_GRAPHQL_URL = "https://api.ilivemylife.io/graphql/v1";
|
|
391
|
+
var ME_QUERY = "{ me { id displayName fullName email currentAvatar { uri } } }";
|
|
392
|
+
function useIlmlProfile(token, opts = {}) {
|
|
393
|
+
const graphqlUrl = opts.graphqlUrl || DEFAULT_GRAPHQL_URL;
|
|
394
|
+
const [state, setState] = (0, import_react4.useState)({
|
|
395
|
+
profile: null,
|
|
396
|
+
loading: false,
|
|
397
|
+
invalid: false
|
|
398
|
+
});
|
|
399
|
+
(0, import_react4.useEffect)(() => {
|
|
400
|
+
if (!token) {
|
|
401
|
+
setState({ profile: null, loading: false, invalid: false });
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
let alive = true;
|
|
405
|
+
setState((s) => ({ ...s, loading: true, invalid: false }));
|
|
406
|
+
(async () => {
|
|
407
|
+
try {
|
|
408
|
+
const res = await fetch(graphqlUrl, {
|
|
409
|
+
method: "POST",
|
|
410
|
+
headers: { "content-type": "application/json", "access-token": token },
|
|
411
|
+
body: JSON.stringify({ query: ME_QUERY })
|
|
412
|
+
});
|
|
413
|
+
if (!alive) return;
|
|
414
|
+
const json = await res.json().catch(() => null);
|
|
415
|
+
const me = json?.data?.me;
|
|
416
|
+
if (me?.id) {
|
|
417
|
+
setState({
|
|
418
|
+
profile: {
|
|
419
|
+
id: me.id,
|
|
420
|
+
name: me.displayName || me.fullName || me.email || "Account",
|
|
421
|
+
avatar: me.currentAvatar?.uri || void 0,
|
|
422
|
+
email: me.email
|
|
423
|
+
},
|
|
424
|
+
loading: false,
|
|
425
|
+
invalid: false
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const code = json?.errors?.[0]?.extensions?.code;
|
|
430
|
+
const authFailed = code === "UNAUTHENTICATED" || res.status === 401 || res.status === 403;
|
|
431
|
+
setState({ profile: null, loading: false, invalid: authFailed });
|
|
432
|
+
} catch {
|
|
433
|
+
if (alive) setState({ profile: null, loading: false, invalid: false });
|
|
434
|
+
}
|
|
435
|
+
})();
|
|
436
|
+
return () => {
|
|
437
|
+
alive = false;
|
|
438
|
+
};
|
|
439
|
+
}, [token, graphqlUrl]);
|
|
440
|
+
return state;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/useIlmlSession.ts
|
|
444
|
+
var import_react5 = require("react");
|
|
445
|
+
var ME_QUERY2 = "{ me { id displayName fullName email currentAvatar { uri } } }";
|
|
446
|
+
var ACCESS_TOKEN_HEADER = "access-token";
|
|
447
|
+
var cfg = null;
|
|
448
|
+
function configure(opts) {
|
|
449
|
+
if (cfg) return;
|
|
450
|
+
cfg = {
|
|
451
|
+
graphqlUrl: opts.graphqlUrl,
|
|
452
|
+
onNewToken: opts.onNewToken,
|
|
453
|
+
tokenKey: opts.tokenKey || "walletAccessToken",
|
|
454
|
+
profileKey: opts.profileKey || "ilmlProfile"
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function getItem(key) {
|
|
458
|
+
try {
|
|
459
|
+
return typeof window !== "undefined" ? window.localStorage.getItem(key) : null;
|
|
460
|
+
} catch {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function setItem(key, value) {
|
|
465
|
+
try {
|
|
466
|
+
window.localStorage.setItem(key, value);
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function removeItem(key) {
|
|
471
|
+
try {
|
|
472
|
+
window.localStorage.removeItem(key);
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function readToken() {
|
|
477
|
+
return cfg ? getItem(cfg.tokenKey) : null;
|
|
478
|
+
}
|
|
479
|
+
function readCachedProfile() {
|
|
480
|
+
if (!cfg) return null;
|
|
481
|
+
const raw = getItem(cfg.profileKey);
|
|
482
|
+
if (!raw) return null;
|
|
483
|
+
try {
|
|
484
|
+
return JSON.parse(raw);
|
|
485
|
+
} catch {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
var sessionCache = { token: null, profile: null };
|
|
490
|
+
var firedTokens = /* @__PURE__ */ new Set();
|
|
491
|
+
function fireNewToken(token) {
|
|
492
|
+
if (!cfg?.onNewToken || firedTokens.has(token)) return;
|
|
493
|
+
firedTokens.add(token);
|
|
494
|
+
try {
|
|
495
|
+
const r = cfg.onNewToken(token);
|
|
496
|
+
if (r && typeof r.then === "function") {
|
|
497
|
+
r.then(void 0, () => firedTokens.delete(token));
|
|
498
|
+
}
|
|
499
|
+
} catch {
|
|
500
|
+
firedTokens.delete(token);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
var sessionListeners = /* @__PURE__ */ new Set();
|
|
504
|
+
var sessionReady = false;
|
|
505
|
+
var sessionInitialized = false;
|
|
506
|
+
function notifySession() {
|
|
507
|
+
sessionListeners.forEach((cb) => cb());
|
|
508
|
+
}
|
|
509
|
+
function subscribeSession(cb) {
|
|
510
|
+
sessionListeners.add(cb);
|
|
511
|
+
return () => {
|
|
512
|
+
sessionListeners.delete(cb);
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function setSessionToken(token) {
|
|
516
|
+
const tokenKey = cfg?.tokenKey || "walletAccessToken";
|
|
517
|
+
const profileKey = cfg?.profileKey || "ilmlProfile";
|
|
518
|
+
if (token) {
|
|
519
|
+
setItem(tokenKey, token);
|
|
520
|
+
fireNewToken(token);
|
|
521
|
+
} else {
|
|
522
|
+
removeItem(tokenKey);
|
|
523
|
+
removeItem(profileKey);
|
|
524
|
+
sessionCache.profile = null;
|
|
525
|
+
}
|
|
526
|
+
sessionCache.token = token;
|
|
527
|
+
notifySession();
|
|
528
|
+
}
|
|
529
|
+
function signOutIlmlSession() {
|
|
530
|
+
setSessionToken(null);
|
|
531
|
+
}
|
|
532
|
+
function getIlmlToken() {
|
|
533
|
+
return sessionCache.token ?? readToken();
|
|
534
|
+
}
|
|
535
|
+
function initSessionOnce() {
|
|
536
|
+
if (sessionInitialized || typeof window === "undefined" || !cfg) return;
|
|
537
|
+
sessionInitialized = true;
|
|
538
|
+
const t = readToken();
|
|
539
|
+
sessionCache.token = t;
|
|
540
|
+
if (t && !sessionCache.profile) sessionCache.profile = readCachedProfile();
|
|
541
|
+
if (t) fireNewToken(t);
|
|
542
|
+
sessionReady = true;
|
|
543
|
+
window.addEventListener("storage", (e) => {
|
|
544
|
+
if (!cfg || e.key !== cfg.tokenKey) return;
|
|
545
|
+
const next = readToken();
|
|
546
|
+
if (next === sessionCache.token) return;
|
|
547
|
+
sessionCache.token = next;
|
|
548
|
+
if (!next) sessionCache.profile = null;
|
|
549
|
+
else fireNewToken(next);
|
|
550
|
+
notifySession();
|
|
551
|
+
});
|
|
552
|
+
notifySession();
|
|
553
|
+
}
|
|
554
|
+
var profileState = { token: null, profile: null, loading: false, invalid: false };
|
|
555
|
+
var profileListeners = /* @__PURE__ */ new Set();
|
|
556
|
+
var profileLatestToken = null;
|
|
557
|
+
function subscribeProfile(cb) {
|
|
558
|
+
profileListeners.add(cb);
|
|
559
|
+
return () => {
|
|
560
|
+
profileListeners.delete(cb);
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function notifyProfile() {
|
|
564
|
+
profileListeners.forEach((cb) => cb());
|
|
565
|
+
}
|
|
566
|
+
async function ensureProfile(token) {
|
|
567
|
+
if (!cfg) return;
|
|
568
|
+
const t = token || null;
|
|
569
|
+
if (!t) {
|
|
570
|
+
profileLatestToken = null;
|
|
571
|
+
if (profileState.token !== null || profileState.profile) {
|
|
572
|
+
profileState = { token: null, profile: null, loading: false, invalid: false };
|
|
573
|
+
notifyProfile();
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (profileState.token === t && profileState.profile && !profileState.loading) return;
|
|
578
|
+
if (profileState.token === t && profileState.invalid) return;
|
|
579
|
+
if (profileLatestToken === t && profileState.loading) return;
|
|
580
|
+
profileLatestToken = t;
|
|
581
|
+
profileState = { token: t, profile: profileState.token === t ? profileState.profile : null, loading: true, invalid: false };
|
|
582
|
+
notifyProfile();
|
|
583
|
+
try {
|
|
584
|
+
const res = await fetch(cfg.graphqlUrl, {
|
|
585
|
+
method: "POST",
|
|
586
|
+
headers: { "content-type": "application/json", [ACCESS_TOKEN_HEADER]: t },
|
|
587
|
+
body: JSON.stringify({ query: ME_QUERY2 })
|
|
588
|
+
});
|
|
589
|
+
const json = await res.json().catch(() => null);
|
|
590
|
+
if (profileLatestToken !== t) return;
|
|
591
|
+
const me = json?.data?.me;
|
|
592
|
+
if (me?.id) {
|
|
593
|
+
const profile = {
|
|
594
|
+
id: me.id,
|
|
595
|
+
name: me.displayName || me.fullName || me.email || "Account",
|
|
596
|
+
avatar: me.currentAvatar?.uri || void 0,
|
|
597
|
+
email: me.email
|
|
598
|
+
};
|
|
599
|
+
sessionCache.profile = profile;
|
|
600
|
+
setItem(cfg.profileKey, JSON.stringify(profile));
|
|
601
|
+
profileState = { token: t, profile, loading: false, invalid: false };
|
|
602
|
+
} else {
|
|
603
|
+
const code = json?.errors?.[0]?.extensions?.code;
|
|
604
|
+
const authFailed = code === "UNAUTHENTICATED" || res.status === 401 || res.status === 403;
|
|
605
|
+
profileState = { token: t, profile: null, loading: false, invalid: authFailed };
|
|
606
|
+
if (authFailed) setSessionToken(null);
|
|
607
|
+
}
|
|
608
|
+
} catch {
|
|
609
|
+
if (profileLatestToken !== t) return;
|
|
610
|
+
profileState = { token: t, profile: profileState.profile, loading: false, invalid: false };
|
|
611
|
+
} finally {
|
|
612
|
+
if (profileLatestToken === t) notifyProfile();
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function useIlmlSessionState() {
|
|
616
|
+
const token = (0, import_react5.useSyncExternalStore)(subscribeSession, () => sessionCache.token, () => null);
|
|
617
|
+
const ready = (0, import_react5.useSyncExternalStore)(subscribeSession, () => sessionReady, () => false);
|
|
618
|
+
const profileSnap = (0, import_react5.useSyncExternalStore)(subscribeProfile, () => profileState, () => profileState);
|
|
619
|
+
(0, import_react5.useEffect)(() => {
|
|
620
|
+
initSessionOnce();
|
|
621
|
+
}, []);
|
|
622
|
+
(0, import_react5.useEffect)(() => {
|
|
623
|
+
ensureProfile(token);
|
|
624
|
+
}, [token]);
|
|
625
|
+
(0, import_react5.useEffect)(() => {
|
|
626
|
+
if (sessionCache.token) fireNewToken(sessionCache.token);
|
|
627
|
+
}, []);
|
|
628
|
+
const t = token || null;
|
|
629
|
+
const profile = profileSnap.token === t ? profileSnap.profile : null;
|
|
630
|
+
return {
|
|
631
|
+
token,
|
|
632
|
+
isLoggedIn: !!token,
|
|
633
|
+
ready,
|
|
634
|
+
// Fresh `me` wins; the cached profile fills the gap while the first fetch is in flight.
|
|
635
|
+
profile: profile || (t ? sessionCache.profile : null),
|
|
636
|
+
profileLoading: profileSnap.loading,
|
|
637
|
+
profileInvalid: profileSnap.invalid
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function useIlmlSession(opts) {
|
|
641
|
+
configure(opts);
|
|
642
|
+
const state = useIlmlSessionState();
|
|
643
|
+
const { openLogin } = useIlmlSso({
|
|
644
|
+
ssoUrl: opts.ssoUrl,
|
|
645
|
+
ssoRoute: opts.ssoRoute,
|
|
646
|
+
clientId: opts.clientId,
|
|
647
|
+
onSsoData: (data) => {
|
|
648
|
+
setSessionToken(data?.accessToken || readToken());
|
|
649
|
+
},
|
|
650
|
+
onClose: opts.onLoginClose
|
|
651
|
+
});
|
|
652
|
+
const logout = (0, import_react5.useCallback)(() => setSessionToken(null), []);
|
|
653
|
+
return { ...state, login: openLogin, logout };
|
|
654
|
+
}
|
|
655
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
656
|
+
0 && (module.exports = {
|
|
657
|
+
IlmlButton,
|
|
658
|
+
IlmlChat,
|
|
659
|
+
IlmlChatWidget,
|
|
660
|
+
getIlmlToken,
|
|
661
|
+
getSsoLoginUrl,
|
|
662
|
+
isValidSsoOrigin,
|
|
663
|
+
popupCenterParams,
|
|
664
|
+
signOutIlmlSession,
|
|
665
|
+
useIlmlProfile,
|
|
666
|
+
useIlmlSession,
|
|
667
|
+
useIlmlSessionState,
|
|
668
|
+
useIlmlSso
|
|
669
|
+
});
|