@ekanos/sdk 0.3.0 → 0.5.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 +171 -75
- package/api-report.md +8 -3
- package/dist/components/base-activation-dialog.d.ts +15 -5
- package/dist/components/base-activation-dialog.js +31 -5
- package/dist/components/base-activation-dialog.js.map +1 -1
- package/dist/components/base-activation-form.d.ts +20 -5
- package/dist/components/base-activation-form.js +12 -5
- package/dist/components/base-activation-form.js.map +1 -1
- package/dist/components/base-marketplace-tile.js +12 -10
- package/dist/components/base-marketplace-tile.js.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/oauth-activation-form.d.ts +23 -16
- package/dist/components/oauth-activation-form.js +34 -20
- package/dist/components/oauth-activation-form.js.map +1 -1
- package/dist/components/widgets/ai-prompt-chip.js +2 -2
- package/dist/components/widgets/ai-prompt-chip.js.map +1 -1
- package/dist/components/widgets/use-relative-time.d.ts +7 -0
- package/dist/components/widgets/use-relative-time.js +55 -0
- package/dist/components/widgets/use-relative-time.js.map +1 -0
- package/dist/components/widgets/widget-ask-bar.js +1 -1
- package/dist/components/widgets/widget-ask-bar.js.map +1 -1
- package/dist/components/widgets/widget-context.d.ts +21 -0
- package/dist/components/widgets/widget-context.js.map +1 -1
- package/dist/components/widgets/widget-preview-provider.d.ts +1 -1
- package/dist/components/widgets/widget-preview-provider.js +1 -1
- package/dist/components/widgets/widget-preview-provider.js.map +1 -1
- package/dist/components/widgets/widget-state-components.d.ts +4 -1
- package/dist/components/widgets/widget-state-components.js +6 -4
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/dist/components/widgets/widget.d.ts +47 -2
- package/dist/components/widgets/widget.js +111 -23
- package/dist/components/widgets/widget.js.map +1 -1
- package/dist/context/egress.js.map +1 -1
- package/dist/context/storage-validation.d.ts +24 -4
- package/dist/context/storage-validation.js +5 -2
- package/dist/context/storage-validation.js.map +1 -1
- package/dist/hooks/ask-assistant-context.js.map +1 -1
- package/dist/hooks/fetch-integration-storage.js.map +1 -1
- package/dist/hooks/partner-oauth.js +3 -0
- package/dist/hooks/partner-oauth.js.map +1 -1
- package/dist/hooks/use-integration-action.js.map +1 -1
- package/dist/hooks/use-oauth-connection-status.d.ts +78 -78
- package/dist/hooks/use-oauth-connection-status.js +17 -10
- package/dist/hooks/use-oauth-connection-status.js.map +1 -1
- package/dist/hooks/widget-event-context.js.map +1 -1
- package/dist/integration/event-surfaces.js.map +1 -1
- package/dist/integration/index.d.ts +2 -2
- package/dist/integration/index.js +1 -1
- package/dist/integration/index.js.map +1 -1
- package/dist/testing/invoke.js +9 -5
- package/dist/testing/invoke.js.map +1 -1
- package/dist/testing/mock-context.js +43 -8
- package/dist/testing/mock-context.js.map +1 -1
- package/dist/testing/mock-fetch-provider.js.map +1 -1
- package/dist/types/integration.d.ts +27 -21
- package/dist/types/integration.js.map +1 -1
- package/dist/types/widget-ask-context.d.ts +9 -0
- package/dist/types/widget-ask-context.js.map +1 -1
- package/dist/types/workspace-target.d.ts +3 -2
- package/dist/types/workspace-target.js.map +1 -1
- package/eslint.preset.mjs +9 -8
- package/package.json +37 -41
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @ekanos/sdk
|
|
2
2
|
|
|
3
3
|
The official SDK for building Ekanos integrations — the packages that add
|
|
4
|
-
widgets, assistant tools, and event handlers to
|
|
4
|
+
widgets, assistant tools, and event handlers to an Ekanos dashboard.
|
|
5
5
|
|
|
6
6
|
**Start here.** This README is the complete authoring reference: everything you
|
|
7
7
|
can declare, with a worked example for each surface. There is no separate docs
|
|
@@ -11,7 +11,7 @@ where to say so.
|
|
|
11
11
|
The other packages, so you know when to leave this page: **`@ekanos/cli`** is
|
|
12
12
|
the toolchain (`init`, `validate`, `dev`, `test`) and owns `ekanos.json`;
|
|
13
13
|
**`@ekanos/harness`** is the local dev harness that renders your surfaces in
|
|
14
|
-
real
|
|
14
|
+
real Ekanos chrome; **`@ekanos/ui`** is the component library; and
|
|
15
15
|
**`@ekanos/integration-schema`** is the validation contract underneath this
|
|
16
16
|
one. Each has its own README.
|
|
17
17
|
|
|
@@ -79,48 +79,44 @@ Three of five icons rendered as `□`. The monorepo could not reproduce it at al
|
|
|
79
79
|
visible from an installed tarball. If you bundle `@ekanos/*` into your own
|
|
80
80
|
output, keep these singletons.
|
|
81
81
|
|
|
82
|
-
### What is published
|
|
82
|
+
### What is published
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
by running a command that 404s:
|
|
84
|
+
Everything you need is on npm:
|
|
86
85
|
|
|
87
|
-
| Package |
|
|
86
|
+
| Package | Role |
|
|
88
87
|
|---|---|
|
|
89
|
-
| `@ekanos/sdk`, `@ekanos/ui`, `@ekanos/integration-schema` |
|
|
90
|
-
| `@ekanos/cli`,
|
|
88
|
+
| `@ekanos/sdk`, `@ekanos/ui`, `@ekanos/integration-schema` | the authoring API, the widget UI slice, the schema |
|
|
89
|
+
| `@ekanos/cli` | `ekanos init`, `dev`, `test`, `validate`, `publish` |
|
|
90
|
+
| `@ekanos/harness` | the local dev harness `ekanos dev` runs |
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
today. The toolchain and the local dev harness are written and documented —
|
|
94
|
-
their READMEs are in this repo and describe what will ship — but you cannot
|
|
95
|
-
install them yet.
|
|
92
|
+
Start a project with:
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
```bash
|
|
95
|
+
npx @ekanos/cli init
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`ekanos init` scaffolds a project that already depends on the matching
|
|
99
|
+
versions, and `ekanos dev` renders every surface of your integration in real
|
|
100
|
+
Ekanos chrome — the dashboard grid, the marketplace tile, the activation form,
|
|
101
|
+
the Triggers panel — from fixtures, or against your real vendor API in live
|
|
102
|
+
mode. The `@ekanos/harness` and `@ekanos/cli` READMEs document both.
|
|
103
|
+
|
|
104
|
+
You can also use `@ekanos/sdk` on its own:
|
|
99
105
|
|
|
100
106
|
```bash
|
|
101
107
|
npm install @ekanos/sdk zod
|
|
102
108
|
```
|
|
103
109
|
|
|
104
110
|
- `defineIntegration()` validates at import time and throws with a remediation
|
|
105
|
-
message, so your definition is its own feedback loop
|
|
106
|
-
`ekanos validate` to know whether it is correct.
|
|
111
|
+
message, so your definition is its own feedback loop.
|
|
107
112
|
- The whole [Testing](#testing) section needs nothing but vitest.
|
|
108
113
|
`createMockContext()` gives you the same context production builds, and
|
|
109
|
-
`invokeWebhook()` / `invokeSchedule()` run your
|
|
110
|
-
payload validation and egress allowlist the real transports
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
and OAuth work collapses back to unit tests, which is a real loss but a
|
|
116
|
-
recoverable one. Widget *appearance* does not: you will be writing it blind
|
|
117
|
-
until the harness ships.
|
|
118
|
-
|
|
119
|
-
One hard stop worth naming: `ekanos.json`, the project file the CLI reads. Its
|
|
120
|
-
schema and the full generated-shell file list are documented in the
|
|
121
|
-
`@ekanos/cli` README — which ships inside the CLI tarball, so today you can
|
|
122
|
-
only read it if you have this repo. Nothing in this document needs that file;
|
|
123
|
-
if you are hand-rolling something that does, wait for the CLI.
|
|
114
|
+
`invokeWebhook()` / `invokeSchedule()` / `invokeActivate()` run your handlers
|
|
115
|
+
with the same payload validation and egress allowlist the real transports
|
|
116
|
+
will.
|
|
117
|
+
|
|
118
|
+
`ekanos.json` is the project file the CLI reads; its schema and the generated
|
|
119
|
+
shell's file list are documented in the `@ekanos/cli` README.
|
|
124
120
|
|
|
125
121
|
## The shape of an integration
|
|
126
122
|
|
|
@@ -316,6 +312,15 @@ Four things the signature does not tell you:
|
|
|
316
312
|
Key names are `[a-z0-9_-]+` with at most one `/` segment: `config`,
|
|
317
313
|
`settings/location`, `cache/recent-payments`. Two slashes is a validation error.
|
|
318
314
|
|
|
315
|
+
**Limits.** The host caps one stored value at **64 KB serialized**, one
|
|
316
|
+
secret value at 64 KB, the number of distinct secret names an integration
|
|
317
|
+
writes at **32**, and a key segment at 100 characters. The same numbers are
|
|
318
|
+
exported as `PARTNER_STORAGE_LIMITS` from `@ekanos/integration-schema` and
|
|
319
|
+
enforced by `createMockContext()`, so a test that writes an oversized value
|
|
320
|
+
fails locally the way the host would. A snapshot of vendor records reaches
|
|
321
|
+
64 KB around 120–150 modest rows: trim to a byte budget and flag it rather
|
|
322
|
+
than finding out on the host.
|
|
323
|
+
|
|
319
324
|
### `egress`
|
|
320
325
|
|
|
321
326
|
An origin-only https allowlist. `ctx.fetch` refuses anything not covered
|
|
@@ -409,7 +414,7 @@ const apiKey = await ctx.secrets.get(ACME_API_KEY);
|
|
|
409
414
|
|
|
410
415
|
### `tools`
|
|
411
416
|
|
|
412
|
-
Tools are what the
|
|
417
|
+
Tools are what the Ekanos assistant (and, where the host allows it, the workflow
|
|
413
418
|
engine) can call. Each declares a name, a description the model reads, an
|
|
414
419
|
optional JSON-Schema parameter object, and an async `run`.
|
|
415
420
|
|
|
@@ -489,7 +494,15 @@ export function CurrentWeatherWidget({ accountId }: IntegrationComponentProps) {
|
|
|
489
494
|
const query = useCurrentWeather(accountId, ctx?.state.state === 'active');
|
|
490
495
|
|
|
491
496
|
return (
|
|
492
|
-
<Widget.DataState
|
|
497
|
+
<Widget.DataState
|
|
498
|
+
loading={query.isLoading}
|
|
499
|
+
error={query.isError}
|
|
500
|
+
// Optional. They add the header menu's Refresh item, a Try again
|
|
501
|
+
// button on the error view, and an "Updated …" footer.
|
|
502
|
+
onRefresh={() => void query.refetch()}
|
|
503
|
+
refreshing={query.isFetching}
|
|
504
|
+
updatedAt={query.dataUpdatedAt}
|
|
505
|
+
>
|
|
493
506
|
<Widget.Card>
|
|
494
507
|
<Widget.Header title="Current conditions" />
|
|
495
508
|
<Widget.Loading />
|
|
@@ -549,7 +562,7 @@ Widget settings live in `ctx.storage`, which is server-side, so a widget reads
|
|
|
549
562
|
them back over HTTP with `fetchIntegrationStorage()` from `@ekanos/sdk/hooks` —
|
|
550
563
|
subject to `clientReadable`, above.
|
|
551
564
|
|
|
552
|
-
To render a widget anywhere other than the
|
|
565
|
+
To render a widget anywhere other than the Ekanos dashboard — your own app, a
|
|
553
566
|
story, a component test — wrap it in `WidgetPreviewProvider`, which fills the
|
|
554
567
|
same `WidgetContext` the host fills, with local state and no persistence:
|
|
555
568
|
|
|
@@ -620,9 +633,17 @@ product — the logo is assigned host-side, not declared here.
|
|
|
620
633
|
|
|
621
634
|
### `components.activationForm`
|
|
622
635
|
|
|
623
|
-
What a user fills in to connect.
|
|
624
|
-
|
|
625
|
-
|
|
636
|
+
What a user fills in to connect — **only** that. The host renders a small
|
|
637
|
+
frame around your form: your product's logo, name and one-line summary, then
|
|
638
|
+
your form. Nothing sits between the two. The host also owns the success
|
|
639
|
+
moment: it closes and toasts, naming the workspace your widgets landed in.
|
|
640
|
+
`BaseActivationForm` renders the action — **Connect** and **Cancel**
|
|
641
|
+
right-aligned under your fields, or, for a form with no fields, a single
|
|
642
|
+
full-width button — plus "Connecting…" while busy and the inline error. You
|
|
643
|
+
supply the `react-hook-form` instance and the fields. Nothing else belongs
|
|
644
|
+
in a form: no title, feature list, permission badge, connected banner or
|
|
645
|
+
success screen. The connect step is kept to the one decision the person
|
|
646
|
+
already made by clicking your product.
|
|
626
647
|
|
|
627
648
|
```tsx
|
|
628
649
|
'use client';
|
|
@@ -654,7 +675,6 @@ export function AcmePaymentsActivationForm({
|
|
|
654
675
|
inline = false, // pass this through — see below
|
|
655
676
|
}: ActivationFormProps) {
|
|
656
677
|
const [error, setError] = useState<string | null>(null);
|
|
657
|
-
const [success, setSuccess] = useState(false);
|
|
658
678
|
|
|
659
679
|
const form = useForm({ resolver: zodResolver(ActivationSchema),
|
|
660
680
|
defaultValues: { [ACME_API_KEY]: '', merchantId: '' } });
|
|
@@ -670,7 +690,8 @@ export function AcmePaymentsActivationForm({
|
|
|
670
690
|
integrationSlug: 'acme-payments',
|
|
671
691
|
activationData: data,
|
|
672
692
|
});
|
|
673
|
-
|
|
693
|
+
// Report once, straight to the host. It closes and toasts.
|
|
694
|
+
onSuccess?.({ success: true, externalId: data.merchantId as string });
|
|
674
695
|
} catch (cause) {
|
|
675
696
|
setError(cause instanceof Error ? cause.message : 'Could not connect.');
|
|
676
697
|
}
|
|
@@ -686,15 +707,6 @@ export function AcmePaymentsActivationForm({
|
|
|
686
707
|
onSubmit={handleSubmit}
|
|
687
708
|
isLoading={isLoading}
|
|
688
709
|
error={error}
|
|
689
|
-
success={success}
|
|
690
|
-
successTitle="Acme Payments is connected"
|
|
691
|
-
onSuccessClose={() => onSuccess?.({
|
|
692
|
-
success: true,
|
|
693
|
-
configData: { merchantId: form.getValues('merchantId') },
|
|
694
|
-
externalId: form.getValues('merchantId'),
|
|
695
|
-
})}
|
|
696
|
-
submitButtonText="Connect Acme Payments"
|
|
697
|
-
submitButtonLoadingText="Connecting…"
|
|
698
710
|
>
|
|
699
711
|
<FormField control={form.control} name={ACME_API_KEY} render={({ field }) => (
|
|
700
712
|
<FormItem>
|
|
@@ -731,36 +743,70 @@ Three things worth knowing:
|
|
|
731
743
|
export const ACME_API_KEY = 'acme_api_key';
|
|
732
744
|
```
|
|
733
745
|
|
|
734
|
-
Values you want
|
|
735
|
-
`ActivationResult` you hand `onSuccess
|
|
746
|
+
Values you want persisted as **settings** (not secrets) go in `configData`
|
|
747
|
+
on the `ActivationResult` you hand `onSuccess`, keyed by the `settings/*`
|
|
748
|
+
storage keys you declared. Never put raw field values there: the host
|
|
749
|
+
rejects any key you did not declare, and the user sees a warning that
|
|
750
|
+
"some settings could not be saved".
|
|
736
751
|
- **`useActivateIntegration` needs the host's injected actions.** It reads them
|
|
737
752
|
from `IntegrationActivationProvider`, which the host mounts. Outside the host
|
|
738
753
|
— in the dev harness, or your own app — mount that provider yourself, or the
|
|
739
754
|
hook has nothing to call.
|
|
740
755
|
|
|
741
|
-
`OAuthActivationForm` is the
|
|
756
|
+
`OAuthActivationForm` is the one-button variant for OAuth integrations: it
|
|
757
|
+
renders **Continue with Acme** with a line explaining the user will sign in on
|
|
758
|
+
Acme's site and come straight back, then activates on return. Its `config`
|
|
759
|
+
needs only `slug` and `displayName` (plus `connectPath`, `errorParam` or
|
|
760
|
+
`autoActivateOnConnect: false` when you genuinely need them).
|
|
742
761
|
`useOAuthConnectionStatus()` reports whether the account is connected.
|
|
743
762
|
|
|
763
|
+
Both primitives accept a `footnote` — one quiet line under the action row for
|
|
764
|
+
what happens next — and nothing else decorative.
|
|
765
|
+
|
|
766
|
+
**Reconnecting.** The host mounts your form a second time, with `reconnect`
|
|
767
|
+
set, when a connected integration needs its credentials renewed — after a
|
|
768
|
+
failed health check, or when the owner chooses Reconnect.
|
|
769
|
+
`OAuthActivationForm` handles it for you: the button reads **Reconnect with
|
|
770
|
+
Acme** and always goes through sign-in again, even with a token stored. A
|
|
771
|
+
credential form needs nothing special: it collects new values and activates
|
|
772
|
+
again, which replaces the old ones and resets the integration's health to
|
|
773
|
+
unknown until the next check. A bespoke OAuth form should treat `reconnect`
|
|
774
|
+
the same way — send the person through sign-in rather than skipping it
|
|
775
|
+
because a token exists.
|
|
776
|
+
|
|
777
|
+
**Behavior change for existing OAuth forms:** `OAuthActivationForm`'s
|
|
778
|
+
`children` render only **after** the user has signed in, and only for a form
|
|
779
|
+
with `autoActivateOnConnect: false` is there a moment to see them. They are
|
|
780
|
+
for post-sign-in fields, not for feature lists or permission copy — the
|
|
781
|
+
connect step carries neither. The primitive does not read those
|
|
782
|
+
fields: a form that needs values after sign-in owns its own `useForm` on
|
|
783
|
+
`BaseActivationForm` and saves them before activating (the GitHub form is
|
|
784
|
+
the reference). If your form passed such a child, delete it; the same copy
|
|
785
|
+
belongs in `permissions` / `capabilities`.
|
|
786
|
+
|
|
744
787
|
A form is optional. Omit it and the host renders a generic one — a plain field
|
|
745
788
|
per value it needs — rather than nothing.
|
|
746
789
|
|
|
747
790
|
### `capabilities` and `permissions`
|
|
748
791
|
|
|
749
|
-
Both are
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
792
|
+
Both are pure data describing what your integration does and touches. They
|
|
793
|
+
are **not** shown on the connect dialog — that step is only your name,
|
|
794
|
+
summary and form. They appear in the platform's product catalog, where a
|
|
795
|
+
source's administrators review an integration before offering it to teams.
|
|
796
|
+
Write `detail` as a sentence you would be happy for a customer's security
|
|
797
|
+
reviewer to read, because that is who reads it. For an OAuth integration,
|
|
798
|
+
make them match the scopes you request: the provider's consent screen is
|
|
799
|
+
what the person connecting actually sees.
|
|
754
800
|
|
|
755
|
-
|
|
801
|
+
The shape each entry takes:
|
|
756
802
|
|
|
757
803
|
| Section | Renders |
|
|
758
804
|
|---|---|
|
|
759
805
|
| **What it does** | `capability.label`, then ` — ` + `capability.description` when present. `capability.icon` is **not** rendered here |
|
|
760
806
|
| **What it can access** | `permission.label`, a badge reading `Read` or `Write` from `permission.type`, then `permission.detail` on the line below |
|
|
761
807
|
|
|
762
|
-
|
|
763
|
-
|
|
808
|
+
An empty array simply declares nothing. `type` drives only the Read/Write
|
|
809
|
+
label — it does not reorder or group anything.
|
|
764
810
|
|
|
765
811
|
```ts
|
|
766
812
|
capabilities: [
|
|
@@ -810,7 +856,8 @@ export const paymentUpdatedWebhook: PartnerWebhookDeclaration<MyStorage> = {
|
|
|
810
856
|
payloadSchema: PaymentUpdatedPayloadSchema,
|
|
811
857
|
|
|
812
858
|
// How the TRANSPORT verifies. `'none'` states explicitly that the source
|
|
813
|
-
// is unsigned. Your handler never checks a signature.
|
|
859
|
+
// is unsigned. Your handler never checks a signature. Add `scheme` when the
|
|
860
|
+
// provider is not a plain HMAC-SHA256 digest of the body — see below.
|
|
814
861
|
signature: { header: 'x-acme-signature', secretName: 'acme_webhook_secret' },
|
|
815
862
|
|
|
816
863
|
// Seeds the harness's payload editor and documents the shape beside the
|
|
@@ -871,6 +918,51 @@ Signature verification is the transport's job in every environment — the host
|
|
|
871
918
|
ingress enforces it, the local harness logs it as skipped. Do not verify it
|
|
872
919
|
yourself; you would be verifying a header the transport already consumed.
|
|
873
920
|
|
|
921
|
+
If a vendor's scheme is not expressible yet and you declare `'none'`, the
|
|
922
|
+
delivery is authenticated only by the endpoint token in its URL. Treat the
|
|
923
|
+
body as a hint: take the ids from it and re-fetch the item through
|
|
924
|
+
`ctx.fetch` before writing anything a widget renders, and confirm removals
|
|
925
|
+
with the vendor too. A forged delivery then costs one read instead of a
|
|
926
|
+
planted name or link on the dashboard.
|
|
927
|
+
|
|
928
|
+
#### `signature.scheme` — when the provider is not a plain body digest
|
|
929
|
+
|
|
930
|
+
Without `scheme`, the transport expects the header to be an HMAC-SHA256 of the
|
|
931
|
+
raw body, hex or base64, optionally labelled `sha256=` (GitHub, Linear).
|
|
932
|
+
Most other providers differ in one of three ways: they
|
|
933
|
+
pack the digest into `k=v` pairs, they sign a timestamp along with the body, or
|
|
934
|
+
they use SHA-1. `scheme` describes that declaratively; the transport does the
|
|
935
|
+
arithmetic and your handler still contains no verification code.
|
|
936
|
+
|
|
937
|
+
| Provider | `scheme` |
|
|
938
|
+
|---|---|
|
|
939
|
+
| Stripe, Calendly (`t=…,v1=…`, HMAC over `"<t>.<body>"`) | `{ format: 'keyed', digestKey: 'v1', timestampKey: 't', signedPayload: '{timestamp}.{body}', toleranceSeconds: 300 }` |
|
|
940
|
+
| Retell (`v=<unix_ms>,d=<hex>`, HMAC over `body + timestamp`) | `{ format: 'keyed', digestKey: 'd', timestampKey: 'v', signedPayload: '{body}{timestamp}' }` |
|
|
941
|
+
| Intercom (`X-Hub-Signature: sha1=…`) | `{ algorithm: 'sha1' }` |
|
|
942
|
+
| Svix-style single signature (`svix-timestamp` header, HMAC over `"<ts>.<body>"`) | `{ timestampHeader: 'svix-timestamp', signedPayload: '{timestamp}.{body}' }` |
|
|
943
|
+
|
|
944
|
+
Fields: `algorithm` (`'sha256'` default, or `'sha1'`), `format` (`'digest'`
|
|
945
|
+
default, or `'keyed'` for `k=v,k=v` headers), `digestKey` / `timestampKey`
|
|
946
|
+
(which pairs, keyed only), `timestampHeader` (a separate header instead of a
|
|
947
|
+
pair), `signedPayload` (a template over `{body}` and `{timestamp}`; default
|
|
948
|
+
`'{body}'`), `toleranceSeconds` (replay window whenever a timestamp source is
|
|
949
|
+
declared; default 300, `0` disables). Seconds and milliseconds are both
|
|
950
|
+
accepted from the sender. `defineIntegration()` rejects a scheme that cannot be
|
|
951
|
+
verified — a keyed format with no `digestKey`, a `{timestamp}` with no source,
|
|
952
|
+
a timestamp source whose value is **not** in `signedPayload` (a replay window
|
|
953
|
+
over an unsigned timestamp protects nothing), a transport header name such as
|
|
954
|
+
`authorization` — so the mistake surfaces before the first delivery, not as a
|
|
955
|
+
401 on it. Linear's replay guard is a field inside the signed body, so for
|
|
956
|
+
Linear you omit `scheme` and check `webhookTimestamp` in the handler.
|
|
957
|
+
|
|
958
|
+
Two things the scheme never does: it never takes the algorithm from the
|
|
959
|
+
header (a `sha1=` label under a `sha256` declaration fails), and it never lets
|
|
960
|
+
a stale delivery through because the digest was right — freshness is checked
|
|
961
|
+
first. Svix-style multi-signature headers (`v1,<b64> v1,<b64>`, space-separated
|
|
962
|
+
with a `whsec_` base64 secret) are not covered yet; declare `'none'` for those
|
|
963
|
+
and say so in the description. Slack (`v0=` label over `v0:{ts}:{body}`) is
|
|
964
|
+
not covered either.
|
|
965
|
+
|
|
874
966
|
### `schedules`
|
|
875
967
|
|
|
876
968
|
A cron-driven run. Same handler shape, same `ctx`.
|
|
@@ -1031,17 +1123,22 @@ either be ignored or aim the host's callback somewhere it should not go.
|
|
|
1031
1123
|
Register this with your provider, against whichever host will run the flow:
|
|
1032
1124
|
|
|
1033
1125
|
```
|
|
1034
|
-
https://<your-
|
|
1126
|
+
https://<your-ekanos-host>/api/integrations/<your-slug>/callback
|
|
1035
1127
|
```
|
|
1036
1128
|
|
|
1037
|
-
The dev harness
|
|
1038
|
-
|
|
1039
|
-
your
|
|
1129
|
+
The dev harness serves its own loopback callback, so you can complete a real
|
|
1130
|
+
round trip locally in live mode: register
|
|
1131
|
+
`http://localhost:3100/api/integrations/<your-slug>/oauth/callback` with your
|
|
1132
|
+
provider as well. See "Local OAuth" in the `@ekanos/harness` README for the
|
|
1133
|
+
secrets file and the connect flow.
|
|
1040
1134
|
|
|
1041
|
-
**
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1135
|
+
In **live mode** the harness runs this loop for real on localhost — authorize
|
|
1136
|
+
redirect, single-use `state`, code exchange through your declared egress, then
|
|
1137
|
+
your `onTokens`. Client credentials come from `.ekanos/local-secrets.json`,
|
|
1138
|
+
which the harness generates and ignores in version control. In fixtures mode it
|
|
1139
|
+
answers connection status from the harness entry instead, and you can still
|
|
1140
|
+
unit-test `onTokens` directly against `createMockContext()` — it is an ordinary
|
|
1141
|
+
async function.
|
|
1045
1142
|
|
|
1046
1143
|
### `proposes`
|
|
1047
1144
|
|
|
@@ -1189,7 +1286,7 @@ and the host adapts the definition internally.
|
|
|
1189
1286
|
|
|
1190
1287
|
### Styling
|
|
1191
1288
|
|
|
1192
|
-
`@ekanos/ui` components emit Tailwind class strings against
|
|
1289
|
+
`@ekanos/ui` components emit Tailwind class strings against Ekanos's semantic
|
|
1193
1290
|
tokens, and icons are Font Awesome glyphs the host loads. Import
|
|
1194
1291
|
`@ekanos/ui/styles.css` (or the narrower `tokens.css` / `theme.css` /
|
|
1195
1292
|
`base.css`) to get the token layer — that also draws a `□` placeholder for
|
|
@@ -1232,12 +1329,11 @@ whether it is you or us costs an afternoon.
|
|
|
1232
1329
|
get an allowlisted fetch, so it needs a small seam of its own — a React
|
|
1233
1330
|
context whose default is the browser's `fetch`. The `@ekanos/harness` README
|
|
1234
1331
|
has the whole ~20-line pattern; the dev harness's live mode mounts it for you.
|
|
1235
|
-
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
wiring lands with no change on your side.
|
|
1332
|
+
- **`onActivate` errors are non-fatal.** The host runs it after an activation
|
|
1333
|
+
is first persisted and again when activation data changes, but a throw is
|
|
1334
|
+
logged and the activation stays connected — use it to seed caches and
|
|
1335
|
+
validate eagerly, never as a connect gate. It also runs in the
|
|
1336
|
+
`@ekanos/harness` activation surface and under `invokeActivate()`.
|
|
1241
1337
|
- **`useActivateIntegration()` is inert without the host's provider.** It reads
|
|
1242
1338
|
server actions out of `IntegrationActivationProvider`. Nothing warns you at
|
|
1243
1339
|
build time.
|
package/api-report.md
CHANGED
|
@@ -9,7 +9,7 @@ This is the complete surface of every entrypoint. The authoring guide
|
|
|
9
9
|
for these symbols is README.md. Entrypoints marked WORKSPACE-ONLY are
|
|
10
10
|
not reachable from a published install — see the note in each section.
|
|
11
11
|
|
|
12
|
-
Total exported names: **
|
|
12
|
+
Total exported names: **172** across 7 entrypoints.
|
|
13
13
|
|
|
14
14
|
## `@ekanos/sdk` (32 exports)
|
|
15
15
|
|
|
@@ -48,7 +48,7 @@ Total exported names: **167** across 7 entrypoints.
|
|
|
48
48
|
| `WidgetSuggestedPrompt` | interface |
|
|
49
49
|
| `WorkspaceTargetDefinition` | interface |
|
|
50
50
|
|
|
51
|
-
## `@ekanos/sdk/components` (
|
|
51
|
+
## `@ekanos/sdk/components` (25 exports)
|
|
52
52
|
|
|
53
53
|
| Export | Kind |
|
|
54
54
|
| --- | --- |
|
|
@@ -68,6 +68,7 @@ Total exported names: **167** across 7 entrypoints.
|
|
|
68
68
|
| `WidgetAskBar` | function |
|
|
69
69
|
| `WidgetContext` | variable |
|
|
70
70
|
| `WidgetContextValue` | interface |
|
|
71
|
+
| `WidgetData` | interface |
|
|
71
72
|
| `WidgetDataLoading` | function |
|
|
72
73
|
| `WidgetHealth` | interface |
|
|
73
74
|
| `WidgetMeta` | interface |
|
|
@@ -190,10 +191,11 @@ Total exported names: **167** across 7 entrypoints.
|
|
|
190
191
|
| `invokeSchedule` | function |
|
|
191
192
|
| `invokeWebhook` | function |
|
|
192
193
|
|
|
193
|
-
## `@ekanos/sdk/integration` (
|
|
194
|
+
## `@ekanos/sdk/integration` (31 exports)
|
|
194
195
|
|
|
195
196
|
| Export | Kind |
|
|
196
197
|
| --- | --- |
|
|
198
|
+
| `DEFAULT_SIGNATURE_TOLERANCE_SECONDS` | variable |
|
|
197
199
|
| `DefinitionCollisionInput` | interface |
|
|
198
200
|
| `FirstPartyInventory` | interface |
|
|
199
201
|
| `IntegrationCapabilityDeclaration` | type alias |
|
|
@@ -205,6 +207,8 @@ Total exported names: **167** across 7 entrypoints.
|
|
|
205
207
|
| `OAuthProviderDeclaration` | interface |
|
|
206
208
|
| `OAuthTokens` | interface |
|
|
207
209
|
| `OnActivateHandler` | type alias |
|
|
210
|
+
| `PARTNER_STORAGE_LIMITS` | variable |
|
|
211
|
+
| `PartnerActionDeclaration` | interface |
|
|
208
212
|
| `PartnerOAuthDeclaration` | interface |
|
|
209
213
|
| `PartnerScheduleDeclaration` | interface |
|
|
210
214
|
| `PartnerToolModule` | interface |
|
|
@@ -218,6 +222,7 @@ Total exported names: **167** across 7 entrypoints.
|
|
|
218
222
|
| `WebhookEvent` | interface |
|
|
219
223
|
| `WebhookResult` | interface |
|
|
220
224
|
| `WebhookSignatureDeclaration` | type alias |
|
|
225
|
+
| `WebhookSignatureScheme` | interface |
|
|
221
226
|
| `defineIntegration` | function |
|
|
222
227
|
| `parseCronExpression` | function |
|
|
223
228
|
| `validateIntegrationDefinitions` | function |
|
|
@@ -16,15 +16,25 @@ interface BaseActivationDialogProps {
|
|
|
16
16
|
inline?: boolean;
|
|
17
17
|
}
|
|
18
18
|
export declare function BaseActivationDialog({ isOpen, onClose, title, description, children, isLoading, error, success, successTitle, successMessage, onSuccessClose, maxWidth, inline, }: BaseActivationDialogProps): import("react").JSX.Element;
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The form's body when it sits inside chrome the HOST owns (the connect
|
|
21
|
+
* dialog, the workspace notice, first-use). The host already shows the
|
|
22
|
+
* integration's name, what it does and what it can access, and it owns
|
|
23
|
+
* the success moment (it closes and toasts) — so this renders the fields,
|
|
24
|
+
* the error, and nothing else.
|
|
25
|
+
*
|
|
26
|
+
* `success` is a hand-off, not a screen: the moment it turns true,
|
|
27
|
+
* `onSuccessClose` runs once, which is what lets a form written for the
|
|
28
|
+
* old "Continue" panel finish the same way as one that reports success
|
|
29
|
+
* directly. Nothing renders after it fires; the host takes over.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ActivationInline({ isLoading, error, success, onSuccessClose, children, }: {
|
|
20
32
|
isLoading?: boolean;
|
|
21
33
|
error?: string | null;
|
|
22
34
|
success?: boolean;
|
|
23
|
-
|
|
24
|
-
successMessage?: string;
|
|
25
|
-
onClose: () => void;
|
|
35
|
+
onSuccessClose?: () => void;
|
|
26
36
|
children: ReactNode;
|
|
27
|
-
}): import("react").JSX.Element;
|
|
37
|
+
}): import("react").JSX.Element | null;
|
|
28
38
|
export declare function ActivationDialog({ isOpen, onClose, title, description, children, isLoading, error, success, successTitle, successMessage, maxWidth, }: {
|
|
29
39
|
isOpen: boolean;
|
|
30
40
|
onClose: () => void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
3
4
|
import { Alert, AlertDescription } from '@ekanos/ui/alert';
|
|
4
5
|
import { Button } from '@ekanos/ui/button';
|
|
5
6
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@ekanos/ui/dialog';
|
|
@@ -12,10 +13,10 @@ function SuccessContent({ successTitle = 'Integration Activated!', successMessag
|
|
|
12
13
|
return (_jsxs("div", { className: "flex flex-col items-center space-y-4 py-6", children: [_jsx("div", { className: "bg-success/10 flex h-16 w-16 items-center justify-center rounded-full", children: _jsx(Icon, { name: "fa-light fa-circle-check", className: "text-success h-8 w-8" }) }), _jsxs("div", { className: "text-center", children: [_jsx("h3", { className: "text-lg font-semibold", children: successTitle }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: successMessage })] }), showSeparator && _jsx(Separator, {}), _jsxs("div", { className: "flex gap-2", children: [showSeparator && (_jsx(Button, { variant: "outline", onClick: onClose, children: _jsx(Trans, { i18nKey: "common:stayOnMarketplace", defaults: "Stay on Marketplace" }) })), _jsx(Button, { onClick: onClose, children: _jsx(Trans, { i18nKey: "common:continue", defaults: "Continue" }) })] })] }));
|
|
13
14
|
}
|
|
14
15
|
function ErrorAlert({ error }) {
|
|
15
|
-
return (_jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: error }) }));
|
|
16
|
+
return (_jsx(Alert, { variant: "destructive", role: "alert", children: _jsx(AlertDescription, { children: error }) }));
|
|
16
17
|
}
|
|
17
18
|
function LoadingOverlay() {
|
|
18
|
-
return (_jsx("div", { className: "bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(Icon, { name: "fa-light fa-spinner", className: "text-primary h-8 w-8 animate-spin" }), _jsx("span", { className: "text-muted-foreground text-sm", children: "Loading..." })] }) }));
|
|
19
|
+
return (_jsx("div", { className: "bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(Icon, { name: "fa-light fa-spinner-third", className: "text-primary h-8 w-8 animate-spin" }), _jsx("span", { className: "text-muted-foreground text-sm", children: "Loading..." })] }) }));
|
|
19
20
|
}
|
|
20
21
|
export function BaseActivationDialog({ isOpen, onClose, title, description, children, isLoading = false, error = null, success = false, successTitle, successMessage, onSuccessClose, maxWidth = 'sm:max-w-[600px]', inline = false, }) {
|
|
21
22
|
const handleClose = () => {
|
|
@@ -27,13 +28,38 @@ export function BaseActivationDialog({ isOpen, onClose, title, description, chil
|
|
|
27
28
|
}
|
|
28
29
|
};
|
|
29
30
|
if (inline) {
|
|
30
|
-
return (_jsx(ActivationInline, { isLoading: isLoading, error: error, success: success,
|
|
31
|
+
return (_jsx(ActivationInline, { isLoading: isLoading, error: error, success: success, onSuccessClose: success ? handleClose : undefined, children: children }));
|
|
31
32
|
}
|
|
32
33
|
return (_jsx(ActivationDialog, { isOpen: isOpen, onClose: handleClose, title: title, description: description, isLoading: isLoading, error: error, success: success, successTitle: successTitle, successMessage: successMessage, maxWidth: maxWidth, children: children }));
|
|
33
34
|
}
|
|
34
35
|
// --- Explicit Variants ---
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The form's body when it sits inside chrome the HOST owns (the connect
|
|
38
|
+
* dialog, the workspace notice, first-use). The host already shows the
|
|
39
|
+
* integration's name, what it does and what it can access, and it owns
|
|
40
|
+
* the success moment (it closes and toasts) — so this renders the fields,
|
|
41
|
+
* the error, and nothing else.
|
|
42
|
+
*
|
|
43
|
+
* `success` is a hand-off, not a screen: the moment it turns true,
|
|
44
|
+
* `onSuccessClose` runs once, which is what lets a form written for the
|
|
45
|
+
* old "Continue" panel finish the same way as one that reports success
|
|
46
|
+
* directly. Nothing renders after it fires; the host takes over.
|
|
47
|
+
*/
|
|
48
|
+
export function ActivationInline({ isLoading = false, error = null, success = false, onSuccessClose, children, }) {
|
|
49
|
+
const handedOff = useRef(false);
|
|
50
|
+
// Hands the success state to the host exactly once. An effect is the
|
|
51
|
+
// right tool here: this reacts to a state transition that the form's own
|
|
52
|
+
// submit handler set, and the host callback it calls navigates.
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!success || !onSuccessClose || handedOff.current)
|
|
55
|
+
return;
|
|
56
|
+
handedOff.current = true;
|
|
57
|
+
onSuccessClose();
|
|
58
|
+
}, [success, onSuccessClose]);
|
|
59
|
+
if (success) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return (_jsxs("div", { className: cn('flex flex-col gap-4', isLoading && 'opacity-70'), "aria-busy": isLoading, children: [_jsx(If, { condition: !!error, children: _jsx(ErrorAlert, { error: error }) }), children] }));
|
|
37
63
|
}
|
|
38
64
|
export function ActivationDialog({ isOpen, onClose, title, description, children, isLoading = false, error = null, success = false, successTitle, successMessage, maxWidth = 'sm:max-w-[600px]', }) {
|
|
39
65
|
return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: _jsxs(DialogContent, { className: `${maxWidth} max-h-[80vh] overflow-y-auto`, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: description })] }), _jsx(If, { condition: !!error, children: _jsx(ErrorAlert, { error: error }) }), _jsx(If, { condition: success, fallback: children, children: _jsx(SuccessContent, { successTitle: successTitle, successMessage: successMessage, onClose: onClose, showSeparator: true }) }), isLoading && _jsx(LoadingOverlay, {})] }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-activation-dialog.js","sourceRoot":"","sources":["../../src/components/base-activation-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { ReactNode } from 'react';\n\nimport { Alert, AlertDescription } from '@ekanos/ui/alert';\nimport { Button } from '@ekanos/ui/button';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@ekanos/ui/dialog';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { Separator } from '@ekanos/ui/separator';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\ninterface BaseActivationDialogProps {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n maxWidth?: string;\n /** @deprecated Use `ActivationInline` instead for inline rendering */\n inline?: boolean;\n}\n\nfunction SuccessContent({\n successTitle = 'Integration Activated!',\n successMessage = 'Your integration is now active and ready to use.',\n onClose,\n showSeparator = false,\n}: {\n successTitle?: string;\n successMessage?: string;\n onClose: () => void;\n showSeparator?: boolean;\n}) {\n return (\n <div className=\"flex flex-col items-center space-y-4 py-6\">\n <div className=\"bg-success/10 flex h-16 w-16 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-check\"\n className=\"text-success h-8 w-8\"\n />\n </div>\n <div className=\"text-center\">\n <h3 className=\"text-lg font-semibold\">{successTitle}</h3>\n <p className=\"text-muted-foreground mt-1 text-sm\">{successMessage}</p>\n </div>\n {showSeparator && <Separator />}\n <div className=\"flex gap-2\">\n {showSeparator && (\n <Button variant=\"outline\" onClick={onClose}>\n <Trans\n i18nKey=\"common:stayOnMarketplace\"\n defaults=\"Stay on Marketplace\"\n />\n </Button>\n )}\n <Button onClick={onClose}>\n <Trans i18nKey=\"common:continue\" defaults=\"Continue\" />\n </Button>\n </div>\n </div>\n );\n}\n\nfunction ErrorAlert({ error }: { error: string }) {\n return (\n <Alert variant=\"destructive\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n );\n}\n\nfunction LoadingOverlay() {\n return (\n <div className=\"bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center\">\n <div className=\"flex flex-col items-center gap-2\">\n <Icon\n name=\"fa-light fa-spinner\"\n className=\"text-primary h-8 w-8 animate-spin\"\n />\n <span className=\"text-muted-foreground text-sm\">Loading...</span>\n </div>\n </div>\n );\n}\n\nexport function BaseActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n maxWidth = 'sm:max-w-[600px]',\n inline = false,\n}: BaseActivationDialogProps) {\n const handleClose = () => {\n if (success && onSuccessClose) {\n onSuccessClose();\n } else {\n onClose();\n }\n };\n\n if (inline) {\n return (\n <ActivationInline\n isLoading={isLoading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={handleClose}\n >\n {children}\n </ActivationInline>\n );\n }\n\n return (\n <ActivationDialog\n isOpen={isOpen}\n onClose={handleClose}\n title={title}\n description={description}\n isLoading={isLoading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n maxWidth={maxWidth}\n >\n {children}\n </ActivationDialog>\n );\n}\n\n// --- Explicit Variants ---\n\nexport function ActivationInline({\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onClose,\n children,\n}: {\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onClose: () => void;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn('space-y-4', isLoading && 'pointer-events-none opacity-60')}\n aria-busy={isLoading}\n >\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n <If condition={success} fallback={children}>\n <SuccessContent\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={onClose}\n />\n </If>\n </div>\n );\n}\n\nexport function ActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n maxWidth = 'sm:max-w-[600px]',\n}: {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n maxWidth?: string;\n}) {\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>\n <DialogContent className={`${maxWidth} max-h-[80vh] overflow-y-auto`}>\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n\n <If condition={success} fallback={children}>\n <SuccessContent\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={onClose}\n showSeparator\n />\n </If>\n\n {isLoading && <LoadingOverlay />}\n </DialogContent>\n </Dialog>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"base-activation-dialog.js","sourceRoot":"","sources":["../../src/components/base-activation-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { ReactNode, useEffect, useRef } from 'react';\n\nimport { Alert, AlertDescription } from '@ekanos/ui/alert';\nimport { Button } from '@ekanos/ui/button';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@ekanos/ui/dialog';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { Separator } from '@ekanos/ui/separator';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\ninterface BaseActivationDialogProps {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n maxWidth?: string;\n /** @deprecated Use `ActivationInline` instead for inline rendering */\n inline?: boolean;\n}\n\nfunction SuccessContent({\n successTitle = 'Integration Activated!',\n successMessage = 'Your integration is now active and ready to use.',\n onClose,\n showSeparator = false,\n}: {\n successTitle?: string;\n successMessage?: string;\n onClose: () => void;\n showSeparator?: boolean;\n}) {\n return (\n <div className=\"flex flex-col items-center space-y-4 py-6\">\n <div className=\"bg-success/10 flex h-16 w-16 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-check\"\n className=\"text-success h-8 w-8\"\n />\n </div>\n <div className=\"text-center\">\n <h3 className=\"text-lg font-semibold\">{successTitle}</h3>\n <p className=\"text-muted-foreground mt-1 text-sm\">{successMessage}</p>\n </div>\n {showSeparator && <Separator />}\n <div className=\"flex gap-2\">\n {showSeparator && (\n <Button variant=\"outline\" onClick={onClose}>\n <Trans\n i18nKey=\"common:stayOnMarketplace\"\n defaults=\"Stay on Marketplace\"\n />\n </Button>\n )}\n <Button onClick={onClose}>\n <Trans i18nKey=\"common:continue\" defaults=\"Continue\" />\n </Button>\n </div>\n </div>\n );\n}\n\nfunction ErrorAlert({ error }: { error: string }) {\n return (\n <Alert variant=\"destructive\" role=\"alert\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n );\n}\n\nfunction LoadingOverlay() {\n return (\n <div className=\"bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center\">\n <div className=\"flex flex-col items-center gap-2\">\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"text-primary h-8 w-8 animate-spin\"\n />\n <span className=\"text-muted-foreground text-sm\">Loading...</span>\n </div>\n </div>\n );\n}\n\nexport function BaseActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n maxWidth = 'sm:max-w-[600px]',\n inline = false,\n}: BaseActivationDialogProps) {\n const handleClose = () => {\n if (success && onSuccessClose) {\n onSuccessClose();\n } else {\n onClose();\n }\n };\n\n if (inline) {\n return (\n <ActivationInline\n isLoading={isLoading}\n error={error}\n success={success}\n onSuccessClose={success ? handleClose : undefined}\n >\n {children}\n </ActivationInline>\n );\n }\n\n return (\n <ActivationDialog\n isOpen={isOpen}\n onClose={handleClose}\n title={title}\n description={description}\n isLoading={isLoading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n maxWidth={maxWidth}\n >\n {children}\n </ActivationDialog>\n );\n}\n\n// --- Explicit Variants ---\n\n/**\n * The form's body when it sits inside chrome the HOST owns (the connect\n * dialog, the workspace notice, first-use). The host already shows the\n * integration's name, what it does and what it can access, and it owns\n * the success moment (it closes and toasts) — so this renders the fields,\n * the error, and nothing else.\n *\n * `success` is a hand-off, not a screen: the moment it turns true,\n * `onSuccessClose` runs once, which is what lets a form written for the\n * old \"Continue\" panel finish the same way as one that reports success\n * directly. Nothing renders after it fires; the host takes over.\n */\nexport function ActivationInline({\n isLoading = false,\n error = null,\n success = false,\n onSuccessClose,\n children,\n}: {\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n onSuccessClose?: () => void;\n children: ReactNode;\n}) {\n const handedOff = useRef(false);\n\n // Hands the success state to the host exactly once. An effect is the\n // right tool here: this reacts to a state transition that the form's own\n // submit handler set, and the host callback it calls navigates.\n useEffect(() => {\n if (!success || !onSuccessClose || handedOff.current) return;\n\n handedOff.current = true;\n onSuccessClose();\n }, [success, onSuccessClose]);\n\n if (success) {\n return null;\n }\n\n return (\n <div\n className={cn('flex flex-col gap-4', isLoading && 'opacity-70')}\n aria-busy={isLoading}\n >\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n {children}\n </div>\n );\n}\n\nexport function ActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n maxWidth = 'sm:max-w-[600px]',\n}: {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n maxWidth?: string;\n}) {\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>\n <DialogContent className={`${maxWidth} max-h-[80vh] overflow-y-auto`}>\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n\n <If condition={success} fallback={children}>\n <SuccessContent\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={onClose}\n showSeparator\n />\n </If>\n\n {isLoading && <LoadingOverlay />}\n </DialogContent>\n </Dialog>\n );\n}\n"]}
|