@eventop/sdk 1.0.2 → 1.0.3

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
@@ -1,32 +1,474 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
1
+ import { createContext, useContext, useRef, useCallback, useEffect, Children, cloneElement, useState } from 'react';
2
+
3
+ /**
4
+ * Root context — holds the global feature registry.
5
+ * Set by EventopAIProvider at the root of the app.
6
+ */
7
+ const EventopRegistryContext = /*#__PURE__*/createContext(null);
8
+
9
+ /**
10
+ * Feature scope context — set by EventopTarget.
11
+ * Tells any EventopStep inside the tree which feature it belongs to,
12
+ * so you can nest EventopStep inside a EventopTarget without repeating
13
+ * the feature id. Also supports explicit feature="id" on EventopStep
14
+ * for steps that live outside their parent EventopTarget in the tree.
15
+ */
16
+ const EventopFeatureScopeContext = /*#__PURE__*/createContext(null);
17
+ function useRegistry() {
18
+ const ctx = useContext(EventopRegistryContext);
19
+ if (!ctx) throw new Error('[EventopAI] Must be used inside <EventopAIProvider>.');
20
+ return ctx;
21
+ }
22
+ function useFeatureScope() {
23
+ return useContext(EventopFeatureScopeContext); // null is fine — steps can declare feature explicitly
24
+ }
25
+
26
+ /**
27
+ * FeatureRegistry
28
+ *
29
+ * The live map of everything currently mounted in the React tree.
30
+ *
31
+ * Features — registered by ShepherdTarget
32
+ * Flow steps — registered by ShepherdStep, attached to a feature by id
33
+ *
34
+ * Both features and steps can live anywhere in the component tree.
35
+ * They don't need to be co-located. A ShepherdStep just needs to know
36
+ * which feature id it belongs to.
37
+ *
38
+ * Nested steps:
39
+ * Steps can themselves have children steps (sub-steps) by passing
40
+ * a parentStep prop to ShepherdStep. This lets you model flows like:
41
+ *
42
+ * Feature: "Create styled text"
43
+ * Step 0: Click Add Text
44
+ * Step 1: Type your content ← parentStep: 0
45
+ * Step 1.0: Select the text ← parentStep: 1, index: 0
46
+ * Step 1.1: Open font picker ← parentStep: 1, index: 1
47
+ * Step 1.2: Choose a font ← parentStep: 1, index: 2
48
+ * Step 2: Click Done
49
+ *
50
+ * In practice most flows are flat (index 0, 1, 2, 3...) but the
51
+ * registry supports nested depth for complex interactions.
52
+ */
53
+ function createFeatureRegistry() {
54
+ // Map<featureId, featureData>
55
+ const features = new Map();
56
+ // Map<featureId, Map<stepKey, stepData>>
57
+ // stepKey is either a number (flat) or "parentIndex.childIndex" (nested)
58
+ const flowSteps = new Map();
59
+ const listeners = new Set();
60
+ function notify() {
61
+ listeners.forEach(fn => fn());
62
+ }
63
+
64
+ // ── Feature registration ─────────────────────────────────────────────────
65
+
66
+ function registerFeature(feature) {
67
+ features.set(feature.id, {
68
+ ...feature,
69
+ _registeredAt: Date.now()
70
+ });
71
+ notify();
72
+ }
73
+ function unregisterFeature(id) {
74
+ features.delete(id);
75
+ flowSteps.delete(id);
76
+ notify();
77
+ }
78
+
79
+ // ── Step registration ────────────────────────────────────────────────────
80
+
81
+ /**
82
+ * Register a flow step.
83
+ *
84
+ * @param {string} featureId - Parent feature id
85
+ * @param {number} index - Position in the flat flow (0, 1, 2…)
86
+ * @param {number|null} parentStep - If set, this is a sub-step of parentStep
87
+ * @param {object} stepData - { selector, waitFor, advanceOn, ... }
88
+ */
89
+ function registerStep(featureId, index, parentStep, stepData) {
90
+ if (!flowSteps.has(featureId)) {
91
+ flowSteps.set(featureId, new Map());
92
+ }
93
+ const key = parentStep != null ? `${parentStep}.${index}` : String(index);
94
+ flowSteps.get(featureId).set(key, {
95
+ ...stepData,
96
+ index,
97
+ parentStep: parentStep ?? null,
98
+ key
99
+ });
100
+ notify();
101
+ }
102
+ function unregisterStep(featureId, index, parentStep) {
103
+ const map = flowSteps.get(featureId);
104
+ if (!map) return;
105
+ const key = parentStep != null ? `${parentStep}.${index}` : String(index);
106
+ map.delete(key);
107
+ if (map.size === 0) flowSteps.delete(featureId);
108
+ notify();
109
+ }
110
+
111
+ // ── Snapshot ─────────────────────────────────────────────────────────────
112
+
113
+ /**
114
+ * Build the flat flow array the SDK core expects.
115
+ *
116
+ * For nested steps we inline sub-steps after their parent step,
117
+ * in index order. This means a flow like:
118
+ *
119
+ * Step 0 (flat)
120
+ * Step 1 (flat)
121
+ * Step 1.0 (sub-step of 1)
122
+ * Step 1.1 (sub-step of 1)
123
+ * Step 2 (flat)
124
+ *
125
+ * becomes the SDK flow: [step0, step1, step1.0, step1.1, step2]
126
+ *
127
+ * The AI writes titles/text for the parent feature.
128
+ * Sub-steps inherit the parent step's text with a "(N/M)" suffix added
129
+ * by the SDK's expandFlowSteps() function.
130
+ */
131
+ function buildFlow(featureId) {
132
+ const map = flowSteps.get(featureId);
133
+ if (!map || map.size === 0) return null;
134
+ const allSteps = Array.from(map.values());
135
+
136
+ // Separate top-level and nested steps
137
+ const topLevel = allSteps.filter(s => s.parentStep == null).sort((a, b) => a.index - b.index);
138
+ const result = [];
139
+ topLevel.forEach(step => {
140
+ result.push(step);
141
+ // Inline any sub-steps after the parent
142
+ const children = allSteps.filter(s => s.parentStep === step.index).sort((a, b) => a.index - b.index);
143
+ result.push(...children);
144
+ });
145
+ return result.length > 0 ? result : null;
146
+ }
147
+
148
+ /**
149
+ * Returns the live feature array in the format the SDK core expects.
150
+ * screen.check() is automatic — mounted = available.
151
+ */
152
+ function snapshot() {
153
+ return Array.from(features.values()).map(feature => {
154
+ const flow = buildFlow(feature.id);
155
+ return {
156
+ id: feature.id,
157
+ name: feature.name,
158
+ description: feature.description,
159
+ selector: feature.selector,
160
+ advanceOn: feature.advanceOn || null,
161
+ waitFor: feature.waitFor || null,
162
+ ...(flow ? {
163
+ flow
164
+ } : {}),
165
+ screen: {
166
+ id: feature.id,
167
+ check: () => features.has(feature.id),
168
+ navigate: feature.navigate || null,
169
+ waitFor: feature.navigateWaitFor || feature.selector || null
170
+ }
171
+ };
172
+ });
173
+ }
174
+ return {
175
+ registerFeature,
176
+ unregisterFeature,
177
+ registerStep,
178
+ unregisterStep,
179
+ snapshot,
180
+ isRegistered: id => features.has(id),
181
+ subscribe: fn => {
182
+ listeners.add(fn);
183
+ return () => listeners.delete(fn);
184
+ }
185
+ };
186
+ }
187
+
188
+ /**
189
+ * EventopProvider
190
+ *
191
+ * Drop this once at the root of your app.
192
+ * Every EventopTarget and EventopStep anywhere in the tree will
193
+ * register with this provider automatically.
194
+ *
195
+ * @example
196
+ * <EventopProvider
197
+ * provider={myServerFetcher}
198
+ * appName="PixelCraft"
199
+ * assistantName="Pixel AI"
200
+ * suggestions={['Add a shadow', 'Export design']}
201
+ * theme={{ mode: 'dark', tokens: { accent: '#6366f1' } }}
202
+ * position={{ corner: 'bottom-right' }}
203
+ * >
204
+ * <App />
205
+ * </EventopProvider>
206
+ */
207
+ function EventopProvider({
208
+ children,
209
+ provider,
210
+ appName,
211
+ assistantName,
212
+ suggestions,
213
+ theme,
214
+ position
215
+ }) {
216
+ if (!provider) throw new Error('[Eventop] <EventopProvider> requires a provider prop.');
217
+ if (!appName) throw new Error('[Eventop] <EventopProvider> requires an appName prop.');
218
+ const registry = useRef(createFeatureRegistry()).current;
219
+ const sdkReady = useRef(false);
220
+ const syncToSDK = useCallback(() => {
221
+ if (!sdkReady.current || !window.Eventop) return;
222
+ window.Eventop._updateConfig?.({
223
+ features: registry.snapshot()
224
+ });
225
+ }, [registry]);
226
+ useEffect(() => {
227
+ function boot() {
228
+ window.Eventop.init({
229
+ provider,
230
+ config: {
231
+ appName,
232
+ assistantName,
233
+ suggestions,
234
+ theme,
235
+ position,
236
+ features: registry.snapshot(),
237
+ _providerName: 'custom'
238
+ }
239
+ });
240
+ sdkReady.current = true;
241
+ syncToSDK();
7
242
  }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.Eventop = void 0;
18
- const client_1 = require("./client");
19
- const checkout_1 = require("./resources/checkout");
20
- const subscriptions_1 = require("./resources/subscriptions");
21
- const webhooks_1 = require("./resources/webhooks");
22
- class Eventop {
23
- constructor(config) {
24
- const client = new client_1.EventopClient(config);
25
- this.checkout = new checkout_1.Checkout(client);
26
- this.subscriptions = new subscriptions_1.Subscriptions(client);
27
- this.webhooks = new webhooks_1.Webhooks(client);
243
+ if (window.Eventop) {
244
+ boot();
245
+ } else {
246
+ const s = document.createElement('script');
247
+ s.src = 'https://unpkg.com/shepherd-ai-sdk/dist/shepherd-ai.umd.js';
248
+ s.onload = boot;
249
+ document.head.appendChild(s);
28
250
  }
251
+ const unsub = registry.subscribe(syncToSDK);
252
+ return () => {
253
+ unsub();
254
+ window.Eventop?.cancelTour();
255
+ };
256
+ }, []);
257
+ const ctx = {
258
+ registerFeature: registry.registerFeature,
259
+ unregisterFeature: registry.unregisterFeature,
260
+ registerStep: registry.registerStep,
261
+ unregisterStep: registry.unregisterStep,
262
+ isRegistered: registry.isRegistered
263
+ };
264
+ return /*#__PURE__*/React.createElement(EventopRegistryContext.Provider, {
265
+ value: ctx
266
+ }, children);
267
+ }
268
+
269
+ /**
270
+ * EventopTarget
271
+ *
272
+ * Wraps any component and registers it as an Eventop feature at the call site.
273
+ * The wrapped component does not need to know about Eventop.
274
+ */
275
+ function EventopTarget({
276
+ children,
277
+ id,
278
+ name,
279
+ description,
280
+ navigate,
281
+ navigateWaitFor,
282
+ advanceOn,
283
+ waitFor,
284
+ ...rest
285
+ }) {
286
+ const registry = useRegistry();
287
+ const ref = useRef(null);
288
+ const dataAttr = `data-evtp-${id}`;
289
+ const selector = `[${dataAttr}]`;
290
+ useEffect(() => {
291
+ if (!id || !name) {
292
+ console.warn('[Eventop] <EventopTarget> requires id and name props.');
293
+ return;
294
+ }
295
+ registry.registerFeature({
296
+ id,
297
+ name,
298
+ description,
299
+ selector,
300
+ navigate,
301
+ navigateWaitFor,
302
+ waitFor,
303
+ advanceOn: advanceOn ? {
304
+ selector,
305
+ ...advanceOn
306
+ } : null
307
+ });
308
+ return () => registry.unregisterFeature(id);
309
+ }, [id, name, description]);
310
+ const child = Children.only(children);
311
+ let wrapped;
312
+ try {
313
+ wrapped = /*#__PURE__*/cloneElement(child, {
314
+ [dataAttr]: '',
315
+ ref: node => {
316
+ ref.current = node;
317
+ const originalRef = child.ref;
318
+ if (typeof originalRef === 'function') originalRef(node);else if (originalRef && 'current' in originalRef) originalRef.current = node;
319
+ }
320
+ });
321
+ } catch {
322
+ wrapped = /*#__PURE__*/React.createElement("span", {
323
+ [dataAttr]: '',
324
+ ref: ref,
325
+ style: {
326
+ display: 'contents'
327
+ }
328
+ }, child);
329
+ }
330
+ return /*#__PURE__*/React.createElement(EventopFeatureScopeContext.Provider, {
331
+ value: id
332
+ }, wrapped);
333
+ }
334
+
335
+ /**
336
+ * EventopStep
337
+ *
338
+ * Registers one step in a multi-step flow. Can live anywhere in the tree.
339
+ * Steps self-assemble into order via the `index` prop.
340
+ */
341
+ function EventopStep({
342
+ children,
343
+ feature,
344
+ index,
345
+ parentStep,
346
+ waitFor,
347
+ advanceOn
348
+ }) {
349
+ const registry = useRegistry();
350
+ const featureScope = useFeatureScope();
351
+ const featureId = feature || featureScope;
352
+ const ref = useRef(null);
353
+ if (!featureId) {
354
+ console.warn('[Eventop] <EventopStep> needs either a feature prop or an <EventopTarget> ancestor.');
355
+ }
356
+ if (index == null) {
357
+ console.warn('[Eventop] <EventopStep> requires an index prop.');
358
+ }
359
+ const dataAttr = `data-evtp-step-${featureId}-${parentStep != null ? `${parentStep}-` : ''}${index}`;
360
+ const selector = `[${dataAttr}]`;
361
+ useEffect(() => {
362
+ if (!featureId || index == null) return;
363
+ registry.registerStep(featureId, index, parentStep ?? null, {
364
+ selector,
365
+ waitFor: waitFor || null,
366
+ advanceOn: advanceOn ? {
367
+ selector,
368
+ ...advanceOn
369
+ } : null
370
+ });
371
+ return () => registry.unregisterStep(featureId, index, parentStep ?? null);
372
+ }, [featureId, index, parentStep]);
373
+ const child = Children.only(children);
374
+ let wrapped;
375
+ try {
376
+ wrapped = /*#__PURE__*/cloneElement(child, {
377
+ [dataAttr]: '',
378
+ ref: node => {
379
+ ref.current = node;
380
+ const originalRef = child.ref;
381
+ if (typeof originalRef === 'function') originalRef(node);else if (originalRef && 'current' in originalRef) originalRef.current = node;
382
+ }
383
+ });
384
+ } catch {
385
+ wrapped = /*#__PURE__*/React.createElement("span", {
386
+ [dataAttr]: '',
387
+ ref: ref,
388
+ style: {
389
+ display: 'contents'
390
+ }
391
+ }, child);
392
+ }
393
+ return wrapped;
394
+ }
395
+
396
+ // ═══════════════════════════════════════════════════════════════════════════
397
+ // useEventopAI
398
+ //
399
+ // Access the SDK programmatic API from inside any component.
400
+ // Use for stepComplete(), stepFail(), open(), close() etc.
401
+ //
402
+ // @example
403
+ // function CheckoutForm() {
404
+ // const { stepComplete, stepFail } = useEventopAI();
405
+ //
406
+ // async function handleNext() {
407
+ // const ok = await validateEmail(email);
408
+ // if (ok) stepComplete();
409
+ // else stepFail('Please enter a valid email address.');
410
+ // }
411
+ // }
412
+ // ═══════════════════════════════════════════════════════════════════════════
413
+
414
+ function useEventop() {
415
+ const sdk = () => window.Eventop;
416
+ return {
417
+ open: () => sdk()?.open(),
418
+ close: () => sdk()?.close(),
419
+ cancelTour: () => sdk()?.cancelTour(),
420
+ resumeTour: () => sdk()?.resumeTour(),
421
+ isActive: () => sdk()?.isActive() ?? false,
422
+ isPaused: () => sdk()?.isPaused() ?? false,
423
+ stepComplete: () => sdk()?.stepComplete(),
424
+ stepFail: msg => sdk()?.stepFail(msg),
425
+ runTour: steps => sdk()?.runTour(steps)
426
+ };
427
+ }
428
+
429
+ // ═══════════════════════════════════════════════════════════════════════════
430
+ // useEventopTour
431
+ //
432
+ // Reactively tracks tour state so you can render your own UI.
433
+ // Polls at 300ms — lightweight enough for a status indicator.
434
+ //
435
+ // @example
436
+ // function TourBar() {
437
+ // const { isActive, isPaused, resume, cancel } = useEventopTour();
438
+ // if (!isActive && !isPaused) return null;
439
+ // return (
440
+ // <div>
441
+ // {isPaused && <button onClick={resume}>Resume tour</button>}
442
+ // <button onClick={cancel}>End</button>
443
+ // </div>
444
+ // );
445
+ // }
446
+ // ═══════════════════════════════════════════════════════════════════════════
447
+
448
+ function useEventopTour() {
449
+ const [state, setState] = useState({
450
+ isActive: false,
451
+ isPaused: false
452
+ });
453
+ useEffect(() => {
454
+ const id = setInterval(() => {
455
+ const sdk = window.Eventop;
456
+ if (!sdk) return;
457
+ const next = {
458
+ isActive: sdk.isActive(),
459
+ isPaused: sdk.isPaused()
460
+ };
461
+ setState(prev => prev.isActive !== next.isActive || prev.isPaused !== next.isPaused ? next : prev);
462
+ }, 300);
463
+ return () => clearInterval(id);
464
+ }, []);
465
+ return {
466
+ ...state,
467
+ resume: useCallback(() => window.Eventop?.resumeTour(), []),
468
+ cancel: useCallback(() => window.Eventop?.cancelTour(), []),
469
+ open: useCallback(() => window.Eventop?.open(), []),
470
+ close: useCallback(() => window.Eventop?.close(), [])
471
+ };
29
472
  }
30
- exports.Eventop = Eventop;
31
- __exportStar(require("./types"), exports);
32
- __exportStar(require("./errors"), exports);
473
+
474
+ export { EventopProvider, EventopStep, EventopTarget, useEventop, useEventopTour };
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@eventop/sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Official SDK for Eventop on-chain subscriptions",
5
- "keywords": ["solana", "subscriptions", "crypto", "payments"],
5
+ "keywords": [
6
+ "solana",
7
+ "subscriptions",
8
+ "crypto",
9
+ "payments"
10
+ ],
6
11
  "homepage": "https://github.com/eventop-s/sdk#readme",
7
12
  "bugs": {
8
13
  "url": "https://github.com/eventop-s/sdk/issues"
@@ -12,15 +17,14 @@
12
17
  "url": "git+https://github.com/eventop-s/sdk.git"
13
18
  },
14
19
  "license": "MIT",
15
- "author": "Eventop",
16
- "main": "./dist/cjs/index.js",
17
- "module": "./dist/esm/index.js",
18
- "types": "./dist/types/index.d.ts",
20
+ "author": "Emengkeng Juslen",
21
+ "type": "module",
22
+ "main": "./dist/index.js",
23
+ "module": "./dist/index.js",
19
24
  "exports": {
20
25
  ".": {
21
- "require": "./dist/cjs/index.js",
22
- "import": "./dist/esm/index.js",
23
- "types": "./dist/types/index.d.ts"
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
24
28
  }
25
29
  },
26
30
  "files": [
@@ -28,18 +32,21 @@
28
32
  "README.md"
29
33
  ],
30
34
  "scripts": {
31
- "build": "npm run build:cjs && npm run build:esm && npm run build:types",
32
- "build:cjs": "tsc --project tsconfig.cjs.json",
33
- "build:esm": "tsc --project tsconfig.esm.json",
34
- "build:types": "tsc --project tsconfig.types.json",
35
- "prepublishOnly": "npm run build"
35
+ "build": "rollup -c",
36
+ "prepublishOnly": "yarn run build"
36
37
  },
37
38
  "dependencies": {
38
39
  "node-fetch": "^2.6.7"
39
40
  },
41
+ "peerDependencies": {
42
+ "react": "^18.0.0 || ^19.0.0"
43
+ },
40
44
  "devDependencies": {
41
- "@types/node": "^18.0.0",
42
- "@types/node-fetch": "^2.6.2",
43
- "typescript": "^5.0.0"
45
+ "@rollup/plugin-node-resolve": "^15.2.3",
46
+ "@rollup/plugin-commonjs": "^25.0.7",
47
+ "@rollup/plugin-babel": "^6.0.4",
48
+ "@babel/core": "^7.23.9",
49
+ "@babel/preset-react": "^7.23.3",
50
+ "rollup": "^4.9.6"
44
51
  }
45
52
  }