plugeen 0.0.4 → 0.0.6

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/dist/index.cjs CHANGED
@@ -20,7 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
- createSdk: () => createSdk
23
+ createPlugeen: () => createPlugeen
24
24
  });
25
25
  module.exports = __toCommonJS(src_exports);
26
26
 
@@ -91,6 +91,7 @@ function getOrCreateIdentity() {
91
91
  var SESSION_KEY = "pl_session";
92
92
  function getOrCreateSessionId() {
93
93
  try {
94
+ if (typeof window === "undefined") return "";
94
95
  const existing = sessionStorage.getItem(SESSION_KEY);
95
96
  if (existing) return existing;
96
97
  throw new Error("No session ID");
@@ -128,122 +129,17 @@ var createApi = (apiKey, options) => {
128
129
  };
129
130
  };
130
131
 
131
- // src/lib/helpers/plugins.ts
132
- var plugins = {
133
- insights: {
134
- registry: "insights",
135
- identifier: "insight.created",
136
- schema: {
137
- queryName: "Checkout funnel",
138
- chartType: "line"
139
- }
140
- },
141
- analytics: {
142
- registry: "analytics",
143
- identifier: "analytics.page_view",
144
- schema: {
145
- event: "page_view",
146
- path: "/pricing",
147
- url: "https://example.com/pricing",
148
- title: "Pricing",
149
- referrer: "https://google.com",
150
- sessionId: "session_01"
151
- }
152
- },
153
- "session.replay": {
154
- registry: "session.replay",
155
- identifier: "session.replay.recording",
156
- schema: {
157
- sessionId: "session_01HX8PDMW3Q8D4",
158
- visitorId: "visitor_01",
159
- durationMs: 184e3,
160
- pageCount: 7,
161
- clickCount: 34,
162
- rageClicks: 2,
163
- deadClicks: 1,
164
- hasError: true,
165
- device: "desktop",
166
- browser: "Chrome",
167
- path: "/checkout"
168
- }
169
- },
170
- "feature.flags": {
171
- registry: "feature.flags",
172
- identifier: "feature.flag.evaluated",
173
- schema: {
174
- flagKey: "new-checkout",
175
- enabled: true,
176
- variant: "treatment",
177
- reason: "rollout",
178
- userId: "user_01",
179
- environment: "production"
180
- }
181
- },
182
- experiments: {
183
- registry: "experiments",
184
- identifier: "experiment.exposed",
185
- schema: {
186
- experimentKey: "pricing-page-test",
187
- variant: "variant-b",
188
- userId: "user_01",
189
- metric: "signup",
190
- converted: true,
191
- value: 49
192
- }
193
- },
194
- surveys: {
195
- registry: "surveys",
196
- identifier: "survey.response",
197
- schema: {
198
- surveyId: "nps-q2",
199
- question: "How likely are you to recommend us?",
200
- response: "The dashboard is fast",
201
- rating: 9,
202
- sentiment: "positive",
203
- userId: "user_01",
204
- path: "/dashboard"
205
- }
206
- },
207
- "log.tracing": {
208
- registry: "log.tracing",
209
- identifier: "log.error",
210
- schema: {
211
- level: "error",
212
- message: "Checkout failed to create payment intent",
213
- traceId: "trace_01HX8PK2B9M8A1",
214
- spanId: "span_checkout",
215
- service: "payments-api",
216
- environment: "production",
217
- route: "POST /api/checkout",
218
- release: "2026.04.25",
219
- runtime: "nodejs",
220
- file: "src/server/checkout.ts",
221
- line: 142,
222
- stack: "Error: payment intent failed"
223
- }
224
- },
225
- "contact.chat": {
226
- registry: "contact.chat",
227
- identifier: "floating.chat.message",
228
- schema: {
229
- id: "chat_123",
230
- origin: "system",
231
- message: "I want some help",
232
- resolved: false
233
- }
234
- }
235
- };
236
-
237
132
  // src/lib/plugins/analytics/index.ts
238
133
  function initAnalytics(api) {
239
134
  const init = () => {
240
- if (typeof window === "undefined") return;
241
135
  const changePage = () => {
242
136
  api.post("/plugins/analytics", {
243
137
  event: "page_view",
244
138
  url: location.href,
245
139
  title: document.title,
246
140
  referrer: document.referrer ?? "",
141
+ screenWidth: window.screen.width,
142
+ screenHeight: window.screen.height,
247
143
  sessionId: getOrCreateSessionId()
248
144
  });
249
145
  };
@@ -252,14 +148,141 @@ function initAnalytics(api) {
252
148
  pushState.apply(this, args);
253
149
  changePage();
254
150
  };
255
- window.addEventListener("popstate", changePage);
151
+ const replaceState = history.replaceState;
152
+ history.replaceState = function(...args) {
153
+ replaceState.apply(this, args);
154
+ changePage;
155
+ };
156
+ window.addEventListener("popstate", () => changePage());
256
157
  changePage();
257
158
  };
258
159
  return init();
259
160
  }
260
161
 
261
162
  // src/lib/plugins/chat/index.tsx
163
+ var import_signals2 = require("@preact/signals");
164
+ var import_lucide_preact = require("lucide-preact");
165
+
166
+ // src/store/theme.ts
167
+ var import_signals = require("@preact/signals");
168
+ var theme = (0, import_signals.signal)({
169
+ accentColor: "",
170
+ foregroundColor: ""
171
+ });
172
+
173
+ // src/components/button.tsx
174
+ var import_jsx_runtime = require("preact/jsx-runtime");
175
+ function Button({ children, disabled, fullWidth }) {
176
+ const deactivated = disabled;
177
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
178
+ "button",
179
+ {
180
+ type: "submit",
181
+ disabled: deactivated,
182
+ style: {
183
+ background: theme.value.accentColor,
184
+ color: theme.value.foregroundColor,
185
+ width: fullWidth ? "100%" : "max-content",
186
+ border: "none",
187
+ borderRadius: "8px",
188
+ padding: "8px 0",
189
+ fontWeight: "600",
190
+ fontSize: "13px",
191
+ opacity: deactivated ? 0.5 : 1,
192
+ cursor: deactivated ? "not allowed" : "pointer",
193
+ transition: "all 0.4s ease"
194
+ },
195
+ children
196
+ }
197
+ );
198
+ }
199
+
200
+ // src/hooks/use-query.ts
262
201
  var import_hooks = require("preact/hooks");
202
+ var cache = /* @__PURE__ */ new Map();
203
+ function serializeKey(key) {
204
+ return typeof key === "string" ? key : JSON.stringify(key);
205
+ }
206
+ function useQuery({
207
+ queryKey,
208
+ queryFn,
209
+ revalidateTime = 0
210
+ }) {
211
+ const key = serializeKey(queryKey);
212
+ const [state, setState] = (0, import_hooks.useState)({
213
+ data: void 0,
214
+ error: void 0,
215
+ isLoading: true,
216
+ isFetching: false
217
+ });
218
+ const mounted = (0, import_hooks.useRef)(true);
219
+ async function fetchData(initial = false) {
220
+ try {
221
+ setState((prev) => ({
222
+ ...prev,
223
+ isLoading: initial,
224
+ isFetching: !initial,
225
+ error: void 0
226
+ }));
227
+ const data = await queryFn();
228
+ cache.set(key, {
229
+ data,
230
+ updatedAt: Date.now()
231
+ });
232
+ if (!mounted.current) return;
233
+ setState({
234
+ data,
235
+ error: void 0,
236
+ isLoading: false,
237
+ isFetching: false
238
+ });
239
+ } catch (error) {
240
+ if (!mounted.current) return;
241
+ setState({
242
+ data: void 0,
243
+ error,
244
+ isLoading: false,
245
+ isFetching: false
246
+ });
247
+ }
248
+ }
249
+ (0, import_hooks.useEffect)(() => {
250
+ mounted.current = true;
251
+ const cached = cache.get(key);
252
+ if (cached) {
253
+ const isStale = Date.now() - cached.updatedAt > revalidateTime;
254
+ setState({
255
+ data: cached.data,
256
+ error: void 0,
257
+ isLoading: false,
258
+ isFetching: isStale
259
+ });
260
+ if (isStale) {
261
+ fetchData(false);
262
+ }
263
+ } else {
264
+ fetchData(true);
265
+ }
266
+ return () => {
267
+ mounted.current = false;
268
+ };
269
+ }, [key]);
270
+ (0, import_hooks.useEffect)(() => {
271
+ let interval;
272
+ if (revalidateTime > 0) {
273
+ interval = setInterval(() => {
274
+ fetchData(false);
275
+ }, revalidateTime);
276
+ }
277
+ return () => {
278
+ clearInterval(interval);
279
+ };
280
+ }, [revalidateTime]);
281
+ return {
282
+ ...state,
283
+ refetch: () => fetchData(false)
284
+ };
285
+ }
263
286
 
264
287
  // src/lib/helpers/ui.ts
265
288
  var import_preact = require("preact");
@@ -277,11 +300,16 @@ function getOrCreateRoot(target, id) {
277
300
  }
278
301
  function renderUI({
279
302
  component: Component,
280
- id
303
+ id,
304
+ options
281
305
  }) {
282
306
  if (typeof document === "undefined") {
283
307
  return { render: () => void 0, unmount: noopUnmount };
284
308
  }
309
+ theme.value = {
310
+ accentColor: options.accentColor,
311
+ foregroundColor: options.foregroundColor
312
+ };
285
313
  const target = document.body;
286
314
  const mount = () => {
287
315
  const container = getOrCreateRoot(document.body, id);
@@ -304,23 +332,35 @@ function renderUI({
304
332
  }
305
333
 
306
334
  // src/lib/plugins/chat/index.tsx
307
- var import_jsx_runtime = require("preact/jsx-runtime");
308
- var pluginName = "contact.chat";
309
- function FloatingChat({ id, api, accentColor }) {
310
- const [open, setOpen] = (0, import_hooks.useState)(false);
311
- const onSubmit = (e) => {
312
- e.preventDefault();
313
- const data = new FormData(e.currentTarget);
314
- const message = String(data.get("message") ?? "");
315
- api.post("/plugins/chat", {
316
- id,
317
- message,
318
- origin: "plugin",
319
- resolved: false
335
+ var import_jsx_runtime2 = require("preact/jsx-runtime");
336
+ var pluginName = "chats";
337
+ var text = (0, import_signals2.signal)("");
338
+ var open = (0, import_signals2.signal)(false);
339
+ var submitting = (0, import_signals2.signal)(false);
340
+ function FloatingChat({
341
+ api,
342
+ options: { accentColor, foregroundColor },
343
+ id
344
+ }) {
345
+ const identity = getStorage().get("identity");
346
+ const { data, refetch, isLoading, error } = useQuery({
347
+ queryKey: [id],
348
+ queryFn: async () => {
349
+ return await api.get("/plugins/chats");
350
+ },
351
+ revalidateTime: 1e4
352
+ });
353
+ const onSubmit = async () => {
354
+ submitting.value = true;
355
+ await api.post("/plugins/chats", {
356
+ text: text.value
320
357
  });
321
- e.currentTarget.reset();
358
+ await refetch();
359
+ text.value = "";
360
+ submitting.value = false;
322
361
  };
323
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
362
+ if (!data && !error && isLoading) return null;
363
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
324
364
  "div",
325
365
  {
326
366
  style: {
@@ -331,7 +371,7 @@ function FloatingChat({ id, api, accentColor }) {
331
371
  fontFamily: "system-ui, sans-serif"
332
372
  },
333
373
  children: [
334
- open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
335
375
  "div",
336
376
  {
337
377
  style: {
@@ -341,10 +381,12 @@ function FloatingChat({ id, api, accentColor }) {
341
381
  boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
342
382
  background: "#fff",
343
383
  overflow: "hidden",
344
- border: "1px solid #e5e7eb"
384
+ maxHeight: open.value ? "500px" : "0px",
385
+ opacity: open.value ? 1 : 0,
386
+ transition: "all 0.4s ease"
345
387
  },
346
388
  children: [
347
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
389
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
348
390
  "div",
349
391
  {
350
392
  style: {
@@ -357,15 +399,73 @@ function FloatingChat({ id, api, accentColor }) {
357
399
  children: "Chat with us"
358
400
  }
359
401
  ),
360
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
402
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
403
+ "div",
404
+ {
405
+ style: {
406
+ display: "grid",
407
+ height: "200px",
408
+ overflowY: "auto",
409
+ padding: "16px",
410
+ gap: "4px"
411
+ },
412
+ children: data?.messages.map((m, index) => {
413
+ const isMine = identity === m.identity.id;
414
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
415
+ "div",
416
+ {
417
+ style: {
418
+ justifySelf: isMine ? "start" : "end",
419
+ width: "max-content"
420
+ },
421
+ children: [
422
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
423
+ "p",
424
+ {
425
+ style: {
426
+ padding: "2px 8px",
427
+ borderRadius: "8px",
428
+ width: "max-content",
429
+ background: isMine ? "silver" : accentColor,
430
+ color: isMine ? "black" : foregroundColor
431
+ },
432
+ children: m.text
433
+ }
434
+ ),
435
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
436
+ "span",
437
+ {
438
+ style: {
439
+ fontSize: "12px",
440
+ color: "silver",
441
+ textAlign: isMine ? "left" : "right"
442
+ },
443
+ children: new Date(m.createdAt).toLocaleDateString()
444
+ }
445
+ )
446
+ ]
447
+ },
448
+ m.text + index.toString()
449
+ );
450
+ })
451
+ }
452
+ ),
453
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
361
454
  "form",
362
455
  {
363
- onSubmit: (values) => onSubmit(values),
456
+ onSubmit: (e) => {
457
+ e.preventDefault();
458
+ onSubmit();
459
+ },
364
460
  style: { padding: "12px 16px" },
365
461
  children: [
366
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
462
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
367
463
  "textarea",
368
464
  {
465
+ value: text.value,
466
+ onInput: (e) => {
467
+ text.value = e.target?.value;
468
+ },
369
469
  name: "message",
370
470
  placeholder: "Send us a message\u2026",
371
471
  rows: 3,
@@ -381,38 +481,24 @@ function FloatingChat({ id, api, accentColor }) {
381
481
  }
382
482
  }
383
483
  ),
384
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
385
- "button",
386
- {
387
- type: "submit",
388
- style: {
389
- marginTop: "8px",
390
- width: "100%",
391
- background: accentColor,
392
- color: "#fff",
393
- border: "none",
394
- borderRadius: "8px",
395
- padding: "8px 0",
396
- fontWeight: "600",
397
- fontSize: "13px",
398
- cursor: "pointer"
399
- },
400
- children: "Send"
401
- }
402
- )
484
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Button, { fullWidth: true, disabled: !text.value, children: submitting.value ? "Sending" : "Send" })
403
485
  ]
404
486
  }
405
487
  )
406
488
  ]
407
489
  }
408
490
  ),
409
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
410
492
  "button",
411
493
  {
412
494
  type: "button",
413
- onClick: () => setOpen((v) => !v),
495
+ onClick: () => {
496
+ open.value = !open.value;
497
+ },
414
498
  "aria-label": "Open chat",
415
499
  style: {
500
+ transform: `rotate(${open.value ? 180 : 0}deg)`,
501
+ transition: "transform 0.4s ease",
416
502
  width: "52px",
417
503
  height: "52px",
418
504
  borderRadius: "50%",
@@ -425,26 +511,7 @@ function FloatingChat({ id, api, accentColor }) {
425
511
  boxShadow: "0 4px 16px rgba(0,0,0,0.18)",
426
512
  marginLeft: "auto"
427
513
  },
428
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
429
- "svg",
430
- {
431
- width: "24",
432
- height: "24",
433
- viewBox: "0 0 24 24",
434
- fill: "none",
435
- stroke: "#fff",
436
- "stroke-width": "2",
437
- "stroke-linecap": "round",
438
- "stroke-linejoin": "round",
439
- children: [
440
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("title", { children: "Svg" }),
441
- open ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
442
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
443
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
444
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
445
- ]
446
- }
447
- )
514
+ children: open.value ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_preact.X, { color: foregroundColor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_preact.MessageCircle, { color: foregroundColor })
448
515
  }
449
516
  )
450
517
  ]
@@ -454,8 +521,9 @@ function FloatingChat({ id, api, accentColor }) {
454
521
  function initChat(api, options) {
455
522
  const id = crypto.randomUUID();
456
523
  const { render } = renderUI({
457
- component: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FloatingChat, { id, api, accentColor: options.accentColor }),
458
- id: pluginName
524
+ component: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FloatingChat, { id, api, options }),
525
+ id: pluginName,
526
+ options
459
527
  });
460
528
  return render();
461
529
  }
@@ -463,7 +531,10 @@ function initChat(api, options) {
463
531
  // src/lib/plugins/events/index.ts
464
532
  function initEvents(api) {
465
533
  return {
466
- create: (eventName, data) => api.post("/events", { name: eventName, data })
534
+ create: (eventName, data) => api.post("/events", {
535
+ name: eventName,
536
+ data
537
+ })
467
538
  };
468
539
  }
469
540
 
@@ -478,7 +549,9 @@ function initExperiments(api) {
478
549
  function initFeatureFlags(api) {
479
550
  return {
480
551
  get: async (flagKey) => {
481
- return api.get(`/plugins/feature-flags/${flagKey}`);
552
+ return api.get(
553
+ `/plugins/feature-flags/${flagKey}`
554
+ );
482
555
  }
483
556
  };
484
557
  }
@@ -512,7 +585,15 @@ function initSurveys(api) {
512
585
  }
513
586
 
514
587
  // src/lib/plugins/index.ts
515
- var getConfigs = (apiKey, options) => {
588
+ var reset = `
589
+ *, *::before, *::after { box-sizing: border-box; margin: 0; }
590
+ body { line-height: 1.5; -webkit-font-smoothing: antialiased; }
591
+ img, picture, video, canvas, svg { display: block; max-width: 100%; }
592
+ input, button, textarea, select { font: inherit; }
593
+ p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
594
+ #root { isolation: isolate; }
595
+ `;
596
+ var initBaseSdk = (apiKey, options) => {
516
597
  const api = createApi(apiKey, options);
517
598
  return {
518
599
  events: initEvents(api),
@@ -523,49 +604,52 @@ var getConfigs = (apiKey, options) => {
523
604
  experiments: initExperiments(api)
524
605
  };
525
606
  };
526
- var getInitializer = (apiKey, options) => {
527
- const allPlugins = Object.keys(plugins);
528
- return allPlugins.map((plugin) => {
529
- const api = createApi(apiKey, options);
530
- return {
531
- analytics: () => initAnalytics(api),
532
- "contact.chat": () => initChat(api, options),
533
- "feature.flags": () => {
534
- },
535
- experiments: () => {
536
- },
537
- "log.tracing": () => {
538
- },
539
- "session.replay": () => {
540
- },
541
- insights: () => {
542
- },
543
- surveys: () => {
544
- }
545
- }[plugin];
546
- });
547
- };
607
+ function initClientSdk(apiKey, options) {
608
+ const isBrowser = typeof window !== "undefined";
609
+ const api = createApi(apiKey, options);
610
+ if (isBrowser) {
611
+ const style = document.createElement("style");
612
+ style.innerHTML = reset;
613
+ document.head.appendChild(style);
614
+ if (options.plugins.includes("chats")) {
615
+ initChat(api, options);
616
+ }
617
+ if (options.plugins.includes("analytics")) {
618
+ initAnalytics(api);
619
+ }
620
+ return {};
621
+ }
622
+ return null;
623
+ }
548
624
 
549
625
  // src/lib/index.ts
550
626
  var defaultOptions = {
551
- baseUrl: "https://plugeen.app/api"
627
+ baseUrl: "https://plugeen.app/api",
628
+ accentColor: "#4f46e5",
629
+ foregroundColor: "#fff",
630
+ plugins: []
552
631
  };
553
- function createSdk(apiKey, options) {
632
+ var baseInstance = null;
633
+ var clientInstance = null;
634
+ function createPlugeen(apiKey, options) {
635
+ const _options = {
636
+ baseUrl: options?.baseUrl || defaultOptions.baseUrl,
637
+ accentColor: options?.accentColor || defaultOptions.accentColor,
638
+ foregroundColor: options?.foregroundColor || defaultOptions.foregroundColor,
639
+ plugins: options?.plugins || []
640
+ };
554
641
  if (!apiKey) {
555
642
  console.warn("[Plugeen] Missing data-api-key attribute.");
556
643
  }
557
- const _options = {
558
- ...defaultOptions,
559
- ...options
560
- };
561
- if (apiKey) {
562
- getInitializer(apiKey, _options).forEach((item) => {
563
- item();
564
- });
644
+ if (!baseInstance) {
645
+ baseInstance = initBaseSdk(apiKey, _options);
646
+ }
647
+ if (!clientInstance) {
648
+ clientInstance = initClientSdk(apiKey, _options);
565
649
  }
566
- return getConfigs(apiKey, _options);
650
+ return baseInstance;
567
651
  }
568
652
  // Annotate the CommonJS export names for ESM import in node:
569
653
  0 && (module.exports = {
570
- createSdk
654
+ createPlugeen
571
655
  });