@postrun/react 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -3
- package/dist/index.cjs +60 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -8
- package/dist/index.d.ts +42 -8
- package/dist/index.js +60 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -211,7 +211,7 @@ declare function useDeleteProfile(): _tanstack_react_query.UseMutationResult<{
|
|
|
211
211
|
/** One account offered for selection — the element type of the accounts list. */
|
|
212
212
|
type DiscoverableAccount = DiscoverableAccountList['data'][number];
|
|
213
213
|
/** Why a connect attempt ended in `error` — actionable reasons for the host. */
|
|
214
|
-
type ConnectErrorReason = 'popup_blocked' | 'auth_failed' | 'connection_not_found' | 'select_failed' | 'reauth_required';
|
|
214
|
+
type ConnectErrorReason = 'prepare_failed' | 'popup_blocked' | 'auth_failed' | 'connection_not_found' | 'select_failed' | 'reauth_required';
|
|
215
215
|
/**
|
|
216
216
|
* The single outcome of a connect attempt. `active` carries the activated
|
|
217
217
|
* connection (so the host can call `onConnected`); `connected_pending` means the
|
|
@@ -338,8 +338,23 @@ interface UseConnectParams {
|
|
|
338
338
|
profileId: string;
|
|
339
339
|
/** The platform to connect (X, LinkedIn, Meta, …). */
|
|
340
340
|
platform: ConnectablePlatform;
|
|
341
|
-
/** Called once a connection is fully ACTIVE (an account is bound).
|
|
341
|
+
/** Called once a connection is fully ACTIVE (an account is bound). The
|
|
342
|
+
* connections list is auto-refetched too, so you rarely need to act here. */
|
|
342
343
|
onConnected?: (connection: Connection) => void;
|
|
344
|
+
/** Called when the attempt fails, with the typed reason. */
|
|
345
|
+
onError?: (reason: ConnectErrorReason) => void;
|
|
346
|
+
/** Called when the user closes the OAuth popup without finishing. The hook
|
|
347
|
+
* stays in the `cancelled` phase; call `reset()` to re-arm for another try. */
|
|
348
|
+
onCancelled?: () => void;
|
|
349
|
+
/**
|
|
350
|
+
* Pre-mint the Nango session on mount (default `true`). Keep it `true` for a
|
|
351
|
+
* dedicated "Connect X" button. Set it `false` for a MULTI-platform picker —
|
|
352
|
+
* then call `prepare()` on the platform button's `onPointerEnter`/`onFocus` so
|
|
353
|
+
* only the platform the user is about to click mints a session (not all of
|
|
354
|
+
* them on open). The popup still needs a pre-minted session, so prepare on
|
|
355
|
+
* intent, not on click.
|
|
356
|
+
*/
|
|
357
|
+
prepareOnMount?: boolean;
|
|
343
358
|
}
|
|
344
359
|
/**
|
|
345
360
|
* The connect flow's UI state. `connected_pending` is a TERMINAL success state —
|
|
@@ -373,10 +388,17 @@ interface UseConnectResult {
|
|
|
373
388
|
/**
|
|
374
389
|
* Start the OAuth flow. MUST be called directly in the user's click handler
|
|
375
390
|
* (no `await` before it): it opens the OAuth popup synchronously, so the
|
|
376
|
-
* browser keeps it inside the user gesture.
|
|
377
|
-
* (
|
|
391
|
+
* browser keeps it inside the user gesture. If the session isn't ready yet
|
|
392
|
+
* (`phase` !== `idle`) it kicks `prepare()` and no-ops the popup, so the next
|
|
393
|
+
* click works — prepare on intent (hover/focus) to make the first click open.
|
|
378
394
|
*/
|
|
379
395
|
start: () => void;
|
|
396
|
+
/**
|
|
397
|
+
* Mint the Nango session ahead of the click. Idempotent (a no-op if a session
|
|
398
|
+
* is already held or a mint is in flight). Only needed with
|
|
399
|
+
* `prepareOnMount: false` — call it on the button's `onPointerEnter`/`onFocus`.
|
|
400
|
+
*/
|
|
401
|
+
prepare: () => void;
|
|
380
402
|
/** When `phase` is `picking`, activate the connection with the chosen account. */
|
|
381
403
|
select: (externalAccountId: string) => void;
|
|
382
404
|
/** Return to a fresh, ready state (re-mints the session) — e.g. a "try again". */
|
|
@@ -397,7 +419,7 @@ interface UseConnectResult {
|
|
|
397
419
|
* The hosted `/connect` page remains the fallback for callers NOT using this SDK
|
|
398
420
|
* (a plain link to `hosted_connect_url`); this hook never redirects.
|
|
399
421
|
*/
|
|
400
|
-
declare function useConnect({ profileId, platform, onConnected, }: UseConnectParams): UseConnectResult;
|
|
422
|
+
declare function useConnect({ profileId, platform, onConnected, onError, onCancelled, prepareOnMount, }: UseConnectParams): UseConnectResult;
|
|
401
423
|
/**
|
|
402
424
|
* List a profile's connected accounts. Pass a `filter` to narrow by `kind`
|
|
403
425
|
* (`posting` = social, `ads`) or `status` — e.g. a composer fetches
|
|
@@ -496,10 +518,15 @@ interface ConnectRenderApi {
|
|
|
496
518
|
state: ConnectState;
|
|
497
519
|
/**
|
|
498
520
|
* Begin connecting. Call this DIRECTLY from your button's `onClick` — it opens
|
|
499
|
-
* the OAuth popup synchronously, so don't `await` anything before it.
|
|
500
|
-
* until the session is ready (`state.phase === 'preparing'`).
|
|
521
|
+
* the OAuth popup synchronously, so don't `await` anything before it.
|
|
501
522
|
*/
|
|
502
523
|
start: () => void;
|
|
524
|
+
/**
|
|
525
|
+
* Mint the session ahead of the click — only needed with
|
|
526
|
+
* `prepareOnMount={false}` (a multi-platform picker): call it on the button's
|
|
527
|
+
* `onPointerEnter`/`onFocus`.
|
|
528
|
+
*/
|
|
529
|
+
prepare: () => void;
|
|
503
530
|
/** When `state.phase === 'picking'`, activate with the chosen account id. */
|
|
504
531
|
select: (externalAccountId: string) => void;
|
|
505
532
|
/** Reset to a fresh, ready state (e.g. a "try again" after an error/cancel). */
|
|
@@ -512,6 +539,13 @@ interface ConnectProps {
|
|
|
512
539
|
platform: ConnectablePlatform;
|
|
513
540
|
/** Called once a connection is fully ACTIVE (an account is bound). */
|
|
514
541
|
onConnected?: (connection: Connection) => void;
|
|
542
|
+
/** Called when the attempt fails, with the typed reason. */
|
|
543
|
+
onError?: (reason: ConnectErrorReason) => void;
|
|
544
|
+
/** Called when the user closes the OAuth popup without finishing. */
|
|
545
|
+
onCancelled?: () => void;
|
|
546
|
+
/** Pre-mint on mount (default `true`). Set `false` for a multi-platform picker
|
|
547
|
+
* and call `prepare()` on intent — see {@link UseConnectParams.prepareOnMount}. */
|
|
548
|
+
prepareOnMount?: boolean;
|
|
515
549
|
/** Render your own button + picker + status from the flow state. */
|
|
516
550
|
children: (api: ConnectRenderApi) => ReactNode;
|
|
517
551
|
}
|
|
@@ -546,7 +580,7 @@ interface ConnectProps {
|
|
|
546
580
|
* The trigger MUST call `start()` directly in the click (it opens the popup
|
|
547
581
|
* synchronously). Mount `<Connect>` inside a `<PostrunProvider>`.
|
|
548
582
|
*/
|
|
549
|
-
declare function Connect({ profileId, platform, onConnected, children, }: ConnectProps): ReactNode;
|
|
583
|
+
declare function Connect({ profileId, platform, onConnected, onError, onCancelled, prepareOnMount, children, }: ConnectProps): ReactNode;
|
|
550
584
|
|
|
551
585
|
type MediaUploadStatus = 'idle' | 'uploading' | 'processing' | 'ready' | 'failed';
|
|
552
586
|
interface MediaUploadOptions {
|
package/dist/index.d.ts
CHANGED
|
@@ -211,7 +211,7 @@ declare function useDeleteProfile(): _tanstack_react_query.UseMutationResult<{
|
|
|
211
211
|
/** One account offered for selection — the element type of the accounts list. */
|
|
212
212
|
type DiscoverableAccount = DiscoverableAccountList['data'][number];
|
|
213
213
|
/** Why a connect attempt ended in `error` — actionable reasons for the host. */
|
|
214
|
-
type ConnectErrorReason = 'popup_blocked' | 'auth_failed' | 'connection_not_found' | 'select_failed' | 'reauth_required';
|
|
214
|
+
type ConnectErrorReason = 'prepare_failed' | 'popup_blocked' | 'auth_failed' | 'connection_not_found' | 'select_failed' | 'reauth_required';
|
|
215
215
|
/**
|
|
216
216
|
* The single outcome of a connect attempt. `active` carries the activated
|
|
217
217
|
* connection (so the host can call `onConnected`); `connected_pending` means the
|
|
@@ -338,8 +338,23 @@ interface UseConnectParams {
|
|
|
338
338
|
profileId: string;
|
|
339
339
|
/** The platform to connect (X, LinkedIn, Meta, …). */
|
|
340
340
|
platform: ConnectablePlatform;
|
|
341
|
-
/** Called once a connection is fully ACTIVE (an account is bound).
|
|
341
|
+
/** Called once a connection is fully ACTIVE (an account is bound). The
|
|
342
|
+
* connections list is auto-refetched too, so you rarely need to act here. */
|
|
342
343
|
onConnected?: (connection: Connection) => void;
|
|
344
|
+
/** Called when the attempt fails, with the typed reason. */
|
|
345
|
+
onError?: (reason: ConnectErrorReason) => void;
|
|
346
|
+
/** Called when the user closes the OAuth popup without finishing. The hook
|
|
347
|
+
* stays in the `cancelled` phase; call `reset()` to re-arm for another try. */
|
|
348
|
+
onCancelled?: () => void;
|
|
349
|
+
/**
|
|
350
|
+
* Pre-mint the Nango session on mount (default `true`). Keep it `true` for a
|
|
351
|
+
* dedicated "Connect X" button. Set it `false` for a MULTI-platform picker —
|
|
352
|
+
* then call `prepare()` on the platform button's `onPointerEnter`/`onFocus` so
|
|
353
|
+
* only the platform the user is about to click mints a session (not all of
|
|
354
|
+
* them on open). The popup still needs a pre-minted session, so prepare on
|
|
355
|
+
* intent, not on click.
|
|
356
|
+
*/
|
|
357
|
+
prepareOnMount?: boolean;
|
|
343
358
|
}
|
|
344
359
|
/**
|
|
345
360
|
* The connect flow's UI state. `connected_pending` is a TERMINAL success state —
|
|
@@ -373,10 +388,17 @@ interface UseConnectResult {
|
|
|
373
388
|
/**
|
|
374
389
|
* Start the OAuth flow. MUST be called directly in the user's click handler
|
|
375
390
|
* (no `await` before it): it opens the OAuth popup synchronously, so the
|
|
376
|
-
* browser keeps it inside the user gesture.
|
|
377
|
-
* (
|
|
391
|
+
* browser keeps it inside the user gesture. If the session isn't ready yet
|
|
392
|
+
* (`phase` !== `idle`) it kicks `prepare()` and no-ops the popup, so the next
|
|
393
|
+
* click works — prepare on intent (hover/focus) to make the first click open.
|
|
378
394
|
*/
|
|
379
395
|
start: () => void;
|
|
396
|
+
/**
|
|
397
|
+
* Mint the Nango session ahead of the click. Idempotent (a no-op if a session
|
|
398
|
+
* is already held or a mint is in flight). Only needed with
|
|
399
|
+
* `prepareOnMount: false` — call it on the button's `onPointerEnter`/`onFocus`.
|
|
400
|
+
*/
|
|
401
|
+
prepare: () => void;
|
|
380
402
|
/** When `phase` is `picking`, activate the connection with the chosen account. */
|
|
381
403
|
select: (externalAccountId: string) => void;
|
|
382
404
|
/** Return to a fresh, ready state (re-mints the session) — e.g. a "try again". */
|
|
@@ -397,7 +419,7 @@ interface UseConnectResult {
|
|
|
397
419
|
* The hosted `/connect` page remains the fallback for callers NOT using this SDK
|
|
398
420
|
* (a plain link to `hosted_connect_url`); this hook never redirects.
|
|
399
421
|
*/
|
|
400
|
-
declare function useConnect({ profileId, platform, onConnected, }: UseConnectParams): UseConnectResult;
|
|
422
|
+
declare function useConnect({ profileId, platform, onConnected, onError, onCancelled, prepareOnMount, }: UseConnectParams): UseConnectResult;
|
|
401
423
|
/**
|
|
402
424
|
* List a profile's connected accounts. Pass a `filter` to narrow by `kind`
|
|
403
425
|
* (`posting` = social, `ads`) or `status` — e.g. a composer fetches
|
|
@@ -496,10 +518,15 @@ interface ConnectRenderApi {
|
|
|
496
518
|
state: ConnectState;
|
|
497
519
|
/**
|
|
498
520
|
* Begin connecting. Call this DIRECTLY from your button's `onClick` — it opens
|
|
499
|
-
* the OAuth popup synchronously, so don't `await` anything before it.
|
|
500
|
-
* until the session is ready (`state.phase === 'preparing'`).
|
|
521
|
+
* the OAuth popup synchronously, so don't `await` anything before it.
|
|
501
522
|
*/
|
|
502
523
|
start: () => void;
|
|
524
|
+
/**
|
|
525
|
+
* Mint the session ahead of the click — only needed with
|
|
526
|
+
* `prepareOnMount={false}` (a multi-platform picker): call it on the button's
|
|
527
|
+
* `onPointerEnter`/`onFocus`.
|
|
528
|
+
*/
|
|
529
|
+
prepare: () => void;
|
|
503
530
|
/** When `state.phase === 'picking'`, activate with the chosen account id. */
|
|
504
531
|
select: (externalAccountId: string) => void;
|
|
505
532
|
/** Reset to a fresh, ready state (e.g. a "try again" after an error/cancel). */
|
|
@@ -512,6 +539,13 @@ interface ConnectProps {
|
|
|
512
539
|
platform: ConnectablePlatform;
|
|
513
540
|
/** Called once a connection is fully ACTIVE (an account is bound). */
|
|
514
541
|
onConnected?: (connection: Connection) => void;
|
|
542
|
+
/** Called when the attempt fails, with the typed reason. */
|
|
543
|
+
onError?: (reason: ConnectErrorReason) => void;
|
|
544
|
+
/** Called when the user closes the OAuth popup without finishing. */
|
|
545
|
+
onCancelled?: () => void;
|
|
546
|
+
/** Pre-mint on mount (default `true`). Set `false` for a multi-platform picker
|
|
547
|
+
* and call `prepare()` on intent — see {@link UseConnectParams.prepareOnMount}. */
|
|
548
|
+
prepareOnMount?: boolean;
|
|
515
549
|
/** Render your own button + picker + status from the flow state. */
|
|
516
550
|
children: (api: ConnectRenderApi) => ReactNode;
|
|
517
551
|
}
|
|
@@ -546,7 +580,7 @@ interface ConnectProps {
|
|
|
546
580
|
* The trigger MUST call `start()` directly in the click (it opens the popup
|
|
547
581
|
* synchronously). Mount `<Connect>` inside a `<PostrunProvider>`.
|
|
548
582
|
*/
|
|
549
|
-
declare function Connect({ profileId, platform, onConnected, children, }: ConnectProps): ReactNode;
|
|
583
|
+
declare function Connect({ profileId, platform, onConnected, onError, onCancelled, prepareOnMount, children, }: ConnectProps): ReactNode;
|
|
550
584
|
|
|
551
585
|
type MediaUploadStatus = 'idle' | 'uploading' | 'processing' | 'ready' | 'failed';
|
|
552
586
|
interface MediaUploadOptions {
|
package/dist/index.js
CHANGED
|
@@ -303,19 +303,28 @@ var POLL_TIMEOUT_MS = 15e3;
|
|
|
303
303
|
function useConnect({
|
|
304
304
|
profileId,
|
|
305
305
|
platform,
|
|
306
|
-
onConnected
|
|
306
|
+
onConnected,
|
|
307
|
+
onError,
|
|
308
|
+
onCancelled,
|
|
309
|
+
prepareOnMount = true
|
|
307
310
|
}) {
|
|
308
|
-
const { client } = usePostrun();
|
|
311
|
+
const { client, queryClient } = usePostrun();
|
|
309
312
|
const [state, setState] = useState({ phase: "preparing" });
|
|
310
313
|
const [remintNonce, setRemintNonce] = useState(0);
|
|
311
314
|
const sessionRef = useRef(null);
|
|
312
315
|
const pickRef = useRef(null);
|
|
313
316
|
const inFlightRef = useRef(false);
|
|
314
317
|
const flowGenRef = useRef(0);
|
|
318
|
+
const preparingRef = useRef(false);
|
|
319
|
+
const prepareGenRef = useRef(0);
|
|
315
320
|
const onConnectedRef = useRef(onConnected);
|
|
321
|
+
const onErrorRef = useRef(onError);
|
|
322
|
+
const onCancelledRef = useRef(onCancelled);
|
|
316
323
|
useEffect(() => {
|
|
317
324
|
onConnectedRef.current = onConnected;
|
|
318
|
-
|
|
325
|
+
onErrorRef.current = onError;
|
|
326
|
+
onCancelledRef.current = onCancelled;
|
|
327
|
+
});
|
|
319
328
|
const abandonFlow = useCallback(() => {
|
|
320
329
|
flowGenRef.current += 1;
|
|
321
330
|
inFlightRef.current = false;
|
|
@@ -323,12 +332,23 @@ function useConnect({
|
|
|
323
332
|
pickRef.current = null;
|
|
324
333
|
pick?.reject(new Error("connect flow abandoned"));
|
|
325
334
|
}, []);
|
|
326
|
-
|
|
327
|
-
|
|
335
|
+
const prepare = useCallback(() => {
|
|
336
|
+
if (sessionRef.current || preparingRef.current) return;
|
|
337
|
+
preparingRef.current = true;
|
|
338
|
+
const gen = prepareGenRef.current;
|
|
328
339
|
setState({ phase: "preparing" });
|
|
329
|
-
|
|
340
|
+
const failPrepare = () => {
|
|
341
|
+
preparingRef.current = false;
|
|
342
|
+
setState({ phase: "error", reason: "prepare_failed" });
|
|
343
|
+
onErrorRef.current?.("prepare_failed");
|
|
344
|
+
};
|
|
330
345
|
connectionsConnect({ client, path: { id: profileId }, body: { platform } }).then(({ data }) => {
|
|
331
|
-
if (
|
|
346
|
+
if (prepareGenRef.current !== gen) return;
|
|
347
|
+
if (!data) {
|
|
348
|
+
failPrepare();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
preparingRef.current = false;
|
|
332
352
|
sessionRef.current = {
|
|
333
353
|
token: data.connect_session_token,
|
|
334
354
|
providerConfigKey: data.provider_config_key,
|
|
@@ -336,16 +356,28 @@ function useConnect({
|
|
|
336
356
|
};
|
|
337
357
|
setState({ phase: "idle" });
|
|
338
358
|
}).catch(() => {
|
|
339
|
-
if (
|
|
359
|
+
if (prepareGenRef.current !== gen) return;
|
|
360
|
+
failPrepare();
|
|
340
361
|
});
|
|
362
|
+
}, [client, profileId, platform]);
|
|
363
|
+
useEffect(() => {
|
|
364
|
+
prepareGenRef.current += 1;
|
|
365
|
+
preparingRef.current = false;
|
|
366
|
+
sessionRef.current = null;
|
|
367
|
+
setState({ phase: "preparing" });
|
|
368
|
+
if (prepareOnMount) prepare();
|
|
341
369
|
return () => {
|
|
342
|
-
|
|
370
|
+
prepareGenRef.current += 1;
|
|
343
371
|
abandonFlow();
|
|
344
372
|
};
|
|
345
|
-
}, [
|
|
373
|
+
}, [profileId, platform, remintNonce, prepareOnMount, prepare, abandonFlow]);
|
|
346
374
|
const start = useCallback(() => {
|
|
347
375
|
const session = sessionRef.current;
|
|
348
|
-
if (!session
|
|
376
|
+
if (!session) {
|
|
377
|
+
prepare();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (inFlightRef.current) return;
|
|
349
381
|
inFlightRef.current = true;
|
|
350
382
|
const gen = flowGenRef.current;
|
|
351
383
|
const isCurrent = () => flowGenRef.current === gen;
|
|
@@ -410,20 +442,24 @@ function useConnect({
|
|
|
410
442
|
switch (outcome.status) {
|
|
411
443
|
case "active":
|
|
412
444
|
setState({ phase: "active", connection: outcome.connection });
|
|
445
|
+
void queryClient.invalidateQueries({ queryKey: connectionKeys.lists() });
|
|
413
446
|
onConnectedRef.current?.(outcome.connection);
|
|
414
447
|
return;
|
|
415
448
|
case "connected_pending":
|
|
416
449
|
setState({ phase: "connected_pending" });
|
|
450
|
+
void queryClient.invalidateQueries({ queryKey: connectionKeys.lists() });
|
|
417
451
|
return;
|
|
418
452
|
case "cancelled":
|
|
419
453
|
setState({ phase: "cancelled" });
|
|
454
|
+
onCancelledRef.current?.();
|
|
420
455
|
return;
|
|
421
456
|
case "error":
|
|
422
457
|
setState({ phase: "error", reason: outcome.reason });
|
|
458
|
+
onErrorRef.current?.(outcome.reason);
|
|
423
459
|
return;
|
|
424
460
|
}
|
|
425
461
|
});
|
|
426
|
-
}, [client, profileId]);
|
|
462
|
+
}, [client, profileId, prepare, queryClient]);
|
|
427
463
|
const select = useCallback((externalAccountId) => {
|
|
428
464
|
const pick = pickRef.current;
|
|
429
465
|
if (!pick) return;
|
|
@@ -434,7 +470,7 @@ function useConnect({
|
|
|
434
470
|
const reset = useCallback(() => {
|
|
435
471
|
setRemintNonce((n) => n + 1);
|
|
436
472
|
}, []);
|
|
437
|
-
return { state, start, select, reset };
|
|
473
|
+
return { state, start, prepare, select, reset };
|
|
438
474
|
}
|
|
439
475
|
function useConnections(profileId, filter) {
|
|
440
476
|
const { client, queryClient } = usePostrun();
|
|
@@ -506,9 +542,19 @@ function Connect({
|
|
|
506
542
|
profileId,
|
|
507
543
|
platform,
|
|
508
544
|
onConnected,
|
|
545
|
+
onError,
|
|
546
|
+
onCancelled,
|
|
547
|
+
prepareOnMount,
|
|
509
548
|
children
|
|
510
549
|
}) {
|
|
511
|
-
const api = useConnect({
|
|
550
|
+
const api = useConnect({
|
|
551
|
+
profileId,
|
|
552
|
+
platform,
|
|
553
|
+
onConnected,
|
|
554
|
+
onError,
|
|
555
|
+
onCancelled,
|
|
556
|
+
prepareOnMount
|
|
557
|
+
});
|
|
512
558
|
return children(api);
|
|
513
559
|
}
|
|
514
560
|
var UploadError = class extends Error {
|