plugeen 0.0.4 → 0.0.7

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.js CHANGED
@@ -65,6 +65,7 @@ function getOrCreateIdentity() {
65
65
  var SESSION_KEY = "pl_session";
66
66
  function getOrCreateSessionId() {
67
67
  try {
68
+ if (typeof window === "undefined") return "";
68
69
  const existing = sessionStorage.getItem(SESSION_KEY);
69
70
  if (existing) return existing;
70
71
  throw new Error("No session ID");
@@ -102,122 +103,17 @@ var createApi = (apiKey, options) => {
102
103
  };
103
104
  };
104
105
 
105
- // src/lib/helpers/plugins.ts
106
- var plugins = {
107
- insights: {
108
- registry: "insights",
109
- identifier: "insight.created",
110
- schema: {
111
- queryName: "Checkout funnel",
112
- chartType: "line"
113
- }
114
- },
115
- analytics: {
116
- registry: "analytics",
117
- identifier: "analytics.page_view",
118
- schema: {
119
- event: "page_view",
120
- path: "/pricing",
121
- url: "https://example.com/pricing",
122
- title: "Pricing",
123
- referrer: "https://google.com",
124
- sessionId: "session_01"
125
- }
126
- },
127
- "session.replay": {
128
- registry: "session.replay",
129
- identifier: "session.replay.recording",
130
- schema: {
131
- sessionId: "session_01HX8PDMW3Q8D4",
132
- visitorId: "visitor_01",
133
- durationMs: 184e3,
134
- pageCount: 7,
135
- clickCount: 34,
136
- rageClicks: 2,
137
- deadClicks: 1,
138
- hasError: true,
139
- device: "desktop",
140
- browser: "Chrome",
141
- path: "/checkout"
142
- }
143
- },
144
- "feature.flags": {
145
- registry: "feature.flags",
146
- identifier: "feature.flag.evaluated",
147
- schema: {
148
- flagKey: "new-checkout",
149
- enabled: true,
150
- variant: "treatment",
151
- reason: "rollout",
152
- userId: "user_01",
153
- environment: "production"
154
- }
155
- },
156
- experiments: {
157
- registry: "experiments",
158
- identifier: "experiment.exposed",
159
- schema: {
160
- experimentKey: "pricing-page-test",
161
- variant: "variant-b",
162
- userId: "user_01",
163
- metric: "signup",
164
- converted: true,
165
- value: 49
166
- }
167
- },
168
- surveys: {
169
- registry: "surveys",
170
- identifier: "survey.response",
171
- schema: {
172
- surveyId: "nps-q2",
173
- question: "How likely are you to recommend us?",
174
- response: "The dashboard is fast",
175
- rating: 9,
176
- sentiment: "positive",
177
- userId: "user_01",
178
- path: "/dashboard"
179
- }
180
- },
181
- "log.tracing": {
182
- registry: "log.tracing",
183
- identifier: "log.error",
184
- schema: {
185
- level: "error",
186
- message: "Checkout failed to create payment intent",
187
- traceId: "trace_01HX8PK2B9M8A1",
188
- spanId: "span_checkout",
189
- service: "payments-api",
190
- environment: "production",
191
- route: "POST /api/checkout",
192
- release: "2026.04.25",
193
- runtime: "nodejs",
194
- file: "src/server/checkout.ts",
195
- line: 142,
196
- stack: "Error: payment intent failed"
197
- }
198
- },
199
- "contact.chat": {
200
- registry: "contact.chat",
201
- identifier: "floating.chat.message",
202
- schema: {
203
- id: "chat_123",
204
- origin: "system",
205
- message: "I want some help",
206
- resolved: false
207
- }
208
- }
209
- };
210
-
211
106
  // src/lib/plugins/analytics/index.ts
212
107
  function initAnalytics(api) {
213
108
  const init = () => {
214
- if (typeof window === "undefined") return;
215
109
  const changePage = () => {
216
110
  api.post("/plugins/analytics", {
217
111
  event: "page_view",
218
112
  url: location.href,
219
113
  title: document.title,
220
114
  referrer: document.referrer ?? "",
115
+ screenWidth: window.screen.width,
116
+ screenHeight: window.screen.height,
221
117
  sessionId: getOrCreateSessionId()
222
118
  });
223
119
  };
@@ -226,14 +122,141 @@ function initAnalytics(api) {
226
122
  pushState.apply(this, args);
227
123
  changePage();
228
124
  };
229
- window.addEventListener("popstate", changePage);
125
+ const replaceState = history.replaceState;
126
+ history.replaceState = function(...args) {
127
+ replaceState.apply(this, args);
128
+ changePage;
129
+ };
130
+ window.addEventListener("popstate", () => changePage());
230
131
  changePage();
231
132
  };
232
133
  return init();
233
134
  }
234
135
 
235
136
  // src/lib/plugins/chat/index.tsx
236
- import { useState } from "preact/hooks";
137
+ import { signal as signal2 } from "@preact/signals";
138
+ import { MessageCircle, X } from "lucide-preact";
139
+
140
+ // src/store/theme.ts
141
+ import { signal } from "@preact/signals";
142
+ var theme = signal({
143
+ accentColor: "",
144
+ foregroundColor: ""
145
+ });
146
+
147
+ // src/components/button.tsx
148
+ import { jsx } from "preact/jsx-runtime";
149
+ function Button({ children, disabled, fullWidth }) {
150
+ const deactivated = disabled;
151
+ return /* @__PURE__ */ jsx(
152
+ "button",
153
+ {
154
+ type: "submit",
155
+ disabled: deactivated,
156
+ style: {
157
+ background: theme.value.accentColor,
158
+ color: theme.value.foregroundColor,
159
+ width: fullWidth ? "100%" : "max-content",
160
+ border: "none",
161
+ borderRadius: "8px",
162
+ padding: "8px 0",
163
+ fontWeight: "600",
164
+ fontSize: "13px",
165
+ opacity: deactivated ? 0.5 : 1,
166
+ cursor: deactivated ? "not allowed" : "pointer",
167
+ transition: "all 0.4s ease"
168
+ },
169
+ children
170
+ }
171
+ );
172
+ }
173
+
174
+ // src/hooks/use-query.ts
175
+ import { useEffect, useRef, useState } from "preact/hooks";
176
+ var cache = /* @__PURE__ */ new Map();
177
+ function serializeKey(key) {
178
+ return typeof key === "string" ? key : JSON.stringify(key);
179
+ }
180
+ function useQuery({
181
+ queryKey,
182
+ queryFn,
183
+ revalidateTime = 0
184
+ }) {
185
+ const key = serializeKey(queryKey);
186
+ const [state, setState] = useState({
187
+ data: void 0,
188
+ error: void 0,
189
+ isLoading: true,
190
+ isFetching: false
191
+ });
192
+ const mounted = useRef(true);
193
+ async function fetchData(initial = false) {
194
+ try {
195
+ setState((prev) => ({
196
+ ...prev,
197
+ isLoading: initial,
198
+ isFetching: !initial,
199
+ error: void 0
200
+ }));
201
+ const data = await queryFn();
202
+ cache.set(key, {
203
+ data,
204
+ updatedAt: Date.now()
205
+ });
206
+ if (!mounted.current) return;
207
+ setState({
208
+ data,
209
+ error: void 0,
210
+ isLoading: false,
211
+ isFetching: false
212
+ });
213
+ } catch (error) {
214
+ if (!mounted.current) return;
215
+ setState({
216
+ data: void 0,
217
+ error,
218
+ isLoading: false,
219
+ isFetching: false
220
+ });
221
+ }
222
+ }
223
+ useEffect(() => {
224
+ mounted.current = true;
225
+ const cached = cache.get(key);
226
+ if (cached) {
227
+ const isStale = Date.now() - cached.updatedAt > revalidateTime;
228
+ setState({
229
+ data: cached.data,
230
+ error: void 0,
231
+ isLoading: false,
232
+ isFetching: isStale
233
+ });
234
+ if (isStale) {
235
+ fetchData(false);
236
+ }
237
+ } else {
238
+ fetchData(true);
239
+ }
240
+ return () => {
241
+ mounted.current = false;
242
+ };
243
+ }, [key]);
244
+ useEffect(() => {
245
+ let interval;
246
+ if (revalidateTime > 0) {
247
+ interval = setInterval(() => {
248
+ fetchData(false);
249
+ }, revalidateTime);
250
+ }
251
+ return () => {
252
+ clearInterval(interval);
253
+ };
254
+ }, [revalidateTime]);
255
+ return {
256
+ ...state,
257
+ refetch: () => fetchData(false)
258
+ };
259
+ }
237
260
 
238
261
  // src/lib/helpers/ui.ts
239
262
  import { render as preactRender } from "preact";
@@ -251,11 +274,16 @@ function getOrCreateRoot(target, id) {
251
274
  }
252
275
  function renderUI({
253
276
  component: Component,
254
- id
277
+ id,
278
+ options
255
279
  }) {
256
280
  if (typeof document === "undefined") {
257
281
  return { render: () => void 0, unmount: noopUnmount };
258
282
  }
283
+ theme.value = {
284
+ accentColor: options.accentColor,
285
+ foregroundColor: options.foregroundColor
286
+ };
259
287
  const target = document.body;
260
288
  const mount = () => {
261
289
  const container = getOrCreateRoot(document.body, id);
@@ -278,22 +306,34 @@ function renderUI({
278
306
  }
279
307
 
280
308
  // src/lib/plugins/chat/index.tsx
281
- import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
282
- var pluginName = "contact.chat";
283
- function FloatingChat({ id, api, accentColor }) {
284
- const [open, setOpen] = useState(false);
285
- const onSubmit = (e) => {
286
- e.preventDefault();
287
- const data = new FormData(e.currentTarget);
288
- const message = String(data.get("message") ?? "");
289
- api.post("/plugins/chat", {
290
- id,
291
- message,
292
- origin: "plugin",
293
- resolved: false
309
+ import { jsx as jsx2, jsxs } from "preact/jsx-runtime";
310
+ var pluginName = "chats";
311
+ var text = signal2("");
312
+ var open = signal2(false);
313
+ var submitting = signal2(false);
314
+ function FloatingChat({
315
+ api,
316
+ options: { accentColor, foregroundColor },
317
+ id
318
+ }) {
319
+ const identity = getStorage().get("identity");
320
+ const { data, refetch, isLoading, error } = useQuery({
321
+ queryKey: [id],
322
+ queryFn: async () => {
323
+ return await api.get("/plugins/chats");
324
+ },
325
+ revalidateTime: 1e4
326
+ });
327
+ const onSubmit = async () => {
328
+ submitting.value = true;
329
+ await api.post("/plugins/chats", {
330
+ text: text.value
294
331
  });
295
- e.currentTarget.reset();
332
+ await refetch();
333
+ text.value = "";
334
+ submitting.value = false;
296
335
  };
336
+ if (!data && !error && isLoading) return null;
297
337
  return /* @__PURE__ */ jsxs(
298
338
  "div",
299
339
  {
@@ -305,7 +345,7 @@ function FloatingChat({ id, api, accentColor }) {
305
345
  fontFamily: "system-ui, sans-serif"
306
346
  },
307
347
  children: [
308
- open && /* @__PURE__ */ jsxs(
348
+ /* @__PURE__ */ jsxs(
309
349
  "div",
310
350
  {
311
351
  style: {
@@ -315,10 +355,12 @@ function FloatingChat({ id, api, accentColor }) {
315
355
  boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
316
356
  background: "#fff",
317
357
  overflow: "hidden",
318
- border: "1px solid #e5e7eb"
358
+ maxHeight: open.value ? "500px" : "0px",
359
+ opacity: open.value ? 1 : 0,
360
+ transition: "all 0.4s ease"
319
361
  },
320
362
  children: [
321
- /* @__PURE__ */ jsx(
363
+ /* @__PURE__ */ jsx2(
322
364
  "div",
323
365
  {
324
366
  style: {
@@ -331,15 +373,73 @@ function FloatingChat({ id, api, accentColor }) {
331
373
  children: "Chat with us"
332
374
  }
333
375
  ),
376
+ /* @__PURE__ */ jsx2(
377
+ "div",
378
+ {
379
+ style: {
380
+ display: "grid",
381
+ height: "200px",
382
+ overflowY: "auto",
383
+ padding: "16px",
384
+ gap: "4px"
385
+ },
386
+ children: data?.messages.map((m, index) => {
387
+ const isMine = identity === m.identity.id;
388
+ return /* @__PURE__ */ jsxs(
389
+ "div",
390
+ {
391
+ style: {
392
+ justifySelf: isMine ? "start" : "end",
393
+ width: "max-content"
394
+ },
395
+ children: [
396
+ /* @__PURE__ */ jsx2(
397
+ "p",
398
+ {
399
+ style: {
400
+ padding: "2px 8px",
401
+ borderRadius: "8px",
402
+ width: "max-content",
403
+ background: isMine ? "silver" : accentColor,
404
+ color: isMine ? "black" : foregroundColor
405
+ },
406
+ children: m.text
407
+ }
408
+ ),
409
+ /* @__PURE__ */ jsx2(
410
+ "span",
411
+ {
412
+ style: {
413
+ fontSize: "12px",
414
+ color: "silver",
415
+ textAlign: isMine ? "left" : "right"
416
+ },
417
+ children: new Date(m.createdAt).toLocaleDateString()
418
+ }
419
+ )
420
+ ]
421
+ },
422
+ m.text + index.toString()
423
+ );
424
+ })
425
+ }
426
+ ),
334
427
  /* @__PURE__ */ jsxs(
335
428
  "form",
336
429
  {
337
- onSubmit: (values) => onSubmit(values),
430
+ onSubmit: (e) => {
431
+ e.preventDefault();
432
+ onSubmit();
433
+ },
338
434
  style: { padding: "12px 16px" },
339
435
  children: [
340
- /* @__PURE__ */ jsx(
436
+ /* @__PURE__ */ jsx2(
341
437
  "textarea",
342
438
  {
439
+ value: text.value,
440
+ onInput: (e) => {
441
+ text.value = e.target?.value;
442
+ },
343
443
  name: "message",
344
444
  placeholder: "Send us a message\u2026",
345
445
  rows: 3,
@@ -355,38 +455,24 @@ function FloatingChat({ id, api, accentColor }) {
355
455
  }
356
456
  }
357
457
  ),
358
- /* @__PURE__ */ jsx(
359
- "button",
360
- {
361
- type: "submit",
362
- style: {
363
- marginTop: "8px",
364
- width: "100%",
365
- background: accentColor,
366
- color: "#fff",
367
- border: "none",
368
- borderRadius: "8px",
369
- padding: "8px 0",
370
- fontWeight: "600",
371
- fontSize: "13px",
372
- cursor: "pointer"
373
- },
374
- children: "Send"
375
- }
376
- )
458
+ /* @__PURE__ */ jsx2(Button, { fullWidth: true, disabled: !text.value, children: submitting.value ? "Sending" : "Send" })
377
459
  ]
378
460
  }
379
461
  )
380
462
  ]
381
463
  }
382
464
  ),
383
- /* @__PURE__ */ jsx(
465
+ /* @__PURE__ */ jsx2(
384
466
  "button",
385
467
  {
386
468
  type: "button",
387
- onClick: () => setOpen((v) => !v),
469
+ onClick: () => {
470
+ open.value = !open.value;
471
+ },
388
472
  "aria-label": "Open chat",
389
473
  style: {
474
+ transform: `rotate(${open.value ? 180 : 0}deg)`,
475
+ transition: "transform 0.4s ease",
390
476
  width: "52px",
391
477
  height: "52px",
392
478
  borderRadius: "50%",
@@ -399,26 +485,7 @@ function FloatingChat({ id, api, accentColor }) {
399
485
  boxShadow: "0 4px 16px rgba(0,0,0,0.18)",
400
486
  marginLeft: "auto"
401
487
  },
402
- children: /* @__PURE__ */ jsxs(
403
- "svg",
404
- {
405
- width: "24",
406
- height: "24",
407
- viewBox: "0 0 24 24",
408
- fill: "none",
409
- stroke: "#fff",
410
- "stroke-width": "2",
411
- "stroke-linecap": "round",
412
- "stroke-linejoin": "round",
413
- children: [
414
- /* @__PURE__ */ jsx("title", { children: "Svg" }),
415
- open ? /* @__PURE__ */ jsxs(Fragment, { children: [
416
- /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
417
- /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
418
- ] }) : /* @__PURE__ */ 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" })
419
- ]
420
- }
421
- )
488
+ children: open.value ? /* @__PURE__ */ jsx2(X, { color: foregroundColor }) : /* @__PURE__ */ jsx2(MessageCircle, { color: foregroundColor })
422
489
  }
423
490
  )
424
491
  ]
@@ -428,8 +495,9 @@ function FloatingChat({ id, api, accentColor }) {
428
495
  function initChat(api, options) {
429
496
  const id = crypto.randomUUID();
430
497
  const { render } = renderUI({
431
- component: /* @__PURE__ */ jsx(FloatingChat, { id, api, accentColor: options.accentColor }),
432
- id: pluginName
498
+ component: /* @__PURE__ */ jsx2(FloatingChat, { id, api, options }),
499
+ id: pluginName,
500
+ options
433
501
  });
434
502
  return render();
435
503
  }
@@ -437,7 +505,10 @@ function initChat(api, options) {
437
505
  // src/lib/plugins/events/index.ts
438
506
  function initEvents(api) {
439
507
  return {
440
- create: (eventName, data) => api.post("/events", { name: eventName, data })
508
+ create: (eventName, data) => api.post("/events", {
509
+ name: eventName,
510
+ data
511
+ })
441
512
  };
442
513
  }
443
514
 
@@ -452,7 +523,9 @@ function initExperiments(api) {
452
523
  function initFeatureFlags(api) {
453
524
  return {
454
525
  get: async (flagKey) => {
455
- return api.get(`/plugins/feature-flags/${flagKey}`);
526
+ return api.get(
527
+ `/plugins/feature-flags/${flagKey}`
528
+ );
456
529
  }
457
530
  };
458
531
  }
@@ -486,7 +559,11 @@ function initSurveys(api) {
486
559
  }
487
560
 
488
561
  // src/lib/plugins/index.ts
489
- var getConfigs = (apiKey, options) => {
562
+ var reset = `
563
+ *, *::before, *::after { box-sizing: border-box; margin: 0; }
564
+ body { line-height: 1.5; -webkit-font-smoothing: antialiased; }
565
+ `;
566
+ var initBaseSdk = (apiKey, options) => {
490
567
  const api = createApi(apiKey, options);
491
568
  return {
492
569
  events: initEvents(api),
@@ -497,48 +574,51 @@ var getConfigs = (apiKey, options) => {
497
574
  experiments: initExperiments(api)
498
575
  };
499
576
  };
500
- var getInitializer = (apiKey, options) => {
501
- const allPlugins = Object.keys(plugins);
502
- return allPlugins.map((plugin) => {
503
- const api = createApi(apiKey, options);
504
- return {
505
- analytics: () => initAnalytics(api),
506
- "contact.chat": () => initChat(api, options),
507
- "feature.flags": () => {
508
- },
509
- experiments: () => {
510
- },
511
- "log.tracing": () => {
512
- },
513
- "session.replay": () => {
514
- },
515
- insights: () => {
516
- },
517
- surveys: () => {
518
- }
519
- }[plugin];
520
- });
521
- };
577
+ function initClientSdk(apiKey, options) {
578
+ const isBrowser = typeof window !== "undefined";
579
+ const api = createApi(apiKey, options);
580
+ if (isBrowser) {
581
+ const style = document.createElement("style");
582
+ style.innerHTML = reset;
583
+ document.head.appendChild(style);
584
+ if (options.plugins.includes("chats")) {
585
+ initChat(api, options);
586
+ }
587
+ if (options.plugins.includes("analytics")) {
588
+ initAnalytics(api);
589
+ }
590
+ return {};
591
+ }
592
+ return null;
593
+ }
522
594
 
523
595
  // src/lib/index.ts
524
596
  var defaultOptions = {
525
- baseUrl: "https://plugeen.app/api"
597
+ baseUrl: "https://plugeen.app/api",
598
+ accentColor: "#4f46e5",
599
+ foregroundColor: "#fff",
600
+ plugins: []
526
601
  };
527
- function createSdk(apiKey, options) {
602
+ var baseInstance = null;
603
+ var clientInstance = null;
604
+ function createPlugeen(apiKey, options) {
605
+ const _options = {
606
+ baseUrl: options?.baseUrl || defaultOptions.baseUrl,
607
+ accentColor: options?.accentColor || defaultOptions.accentColor,
608
+ foregroundColor: options?.foregroundColor || defaultOptions.foregroundColor,
609
+ plugins: options?.plugins || []
610
+ };
528
611
  if (!apiKey) {
529
612
  console.warn("[Plugeen] Missing data-api-key attribute.");
530
613
  }
531
- const _options = {
532
- ...defaultOptions,
533
- ...options
534
- };
535
- if (apiKey) {
536
- getInitializer(apiKey, _options).forEach((item) => {
537
- item();
538
- });
614
+ if (!baseInstance) {
615
+ baseInstance = initBaseSdk(apiKey, _options);
616
+ }
617
+ if (!clientInstance) {
618
+ clientInstance = initClientSdk(apiKey, _options);
539
619
  }
540
- return getConfigs(apiKey, _options);
620
+ return baseInstance;
541
621
  }
542
622
  export {
543
- createSdk
623
+ createPlugeen
544
624
  };