@withone/connect 0.15.0 → 0.16.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/src/flow.ts CHANGED
@@ -156,12 +156,6 @@ export function createConnectFlow(
156
156
  };
157
157
  }
158
158
 
159
- /** @deprecated Renamed to `createConnectFlow` (it is not a React hook;
160
- * React apps can use `useOneConnect` from `@withone/connect/react`).
161
- * Removed in the next minor. */
162
- export const useOneConnect = (options: OneConnectFlowOptions): OneConnectFlow =>
163
- createConnectFlow(options);
164
-
165
159
  /** Test seam: forget this page load's outcome. */
166
160
  export function resetPageReturnForTests(): void {
167
161
  pageReturn = undefined;
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { createConnectFlow, readConnectReturn, useOneConnect } from "./flow";
1
+ export { createConnectFlow, readConnectReturn } from "./flow";
2
2
  export {
3
3
  mountConnectButton,
4
4
  registerConnectButton,
@@ -22,8 +22,3 @@ export type {
22
22
  OneConnectTheme,
23
23
  } from "./types";
24
24
 
25
- import type { OneConnectFlow, OneConnectFlowOptions } from "./types";
26
- /** @deprecated Renamed to `OneConnectFlowOptions`; removed in the next minor. */
27
- export type OneConnectOptions = OneConnectFlowOptions;
28
- /** @deprecated Renamed to `OneConnectFlow`; removed in the next minor. */
29
- export type OneConnectHandle = OneConnectFlow;
package/src/next.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * That serves /api/one/authorize and /api/one/callback. Register
15
15
  * `https://yourapp.com/api/one/callback` as the app's redirect URI.
16
16
  */
17
- import type { OneConnectClient } from "./server";
17
+ import type { CompleteAuthorizationResult, OneConnectClient } from "./server";
18
18
 
19
19
  export interface OneConnectRoutesOptions {
20
20
  /** The app's own id for the signed-in user, or null when nobody is
@@ -25,6 +25,15 @@ export interface OneConnectRoutesOptions {
25
25
  /** Where to send the browser when nobody is signed in. Answers 401
26
26
  * when omitted. */
27
27
  signInUrl?: string;
28
+ /** Called after every callback, before the browser is redirected:
29
+ * `result.outcome` is "connected", "declined" or "failed", and
30
+ * `result.message` says why a flow failed. For your logs and metrics;
31
+ * never show the message to the user. An error it throws is ignored,
32
+ * so the user still lands back in the app. */
33
+ onComplete?: (event: {
34
+ userId: string;
35
+ result: CompleteAuthorizationResult;
36
+ }) => void | Promise<void>;
28
37
  }
29
38
 
30
39
  export interface OneConnectRoutes {
@@ -114,6 +123,13 @@ export function createOneConnectRoutes(
114
123
  url: request.url,
115
124
  getCookie: (name) => readCookie(request, name),
116
125
  });
126
+ if (options.onComplete) {
127
+ try {
128
+ await options.onComplete({ userId: user, result });
129
+ } catch {
130
+ /* the app's own hook threw; the redirect still happens */
131
+ }
132
+ }
117
133
  return redirectWithCookies(
118
134
  result.redirectUrl,
119
135
  result.clearCookieName
package/src/node.ts CHANGED
@@ -29,8 +29,12 @@ export interface OneConnectNodeOptions {
29
29
  loginHintFor?: (
30
30
  request: IncomingMessage,
31
31
  ) => Promise<string | null> | string | null;
32
- /** Where to send the browser when nobody is signed in. */
32
+ /** Where to send the browser when nobody is signed in. Answers 401
33
+ * when omitted. */
33
34
  signInUrl?: string;
35
+ /** Called after every callback with how it ended; see
36
+ * `OneConnectRoutesOptions.onComplete`. */
37
+ onComplete?: OneConnectRoutesOptions["onComplete"];
34
38
  }
35
39
 
36
40
  export type NodeHandler = (
@@ -80,27 +84,31 @@ export function createOneConnectHandlers(
80
84
  options: OneConnectNodeOptions,
81
85
  ): OneConnectHandlers {
82
86
  // The web adapter receives a Request; the Node callbacks want the
83
- // original IncomingMessage, so it is carried alongside by closure.
84
- let current: IncomingMessage | null = null;
85
- const routeOptions: OneConnectRoutesOptions = {
86
- identifyUser: () => options.identifyUser(current as IncomingMessage),
87
- loginHintFor: options.loginHintFor
88
- ? () => options.loginHintFor!(current as IncomingMessage)
87
+ // original IncomingMessage. Each Request maps to its own message, so
88
+ // concurrent requests never read each other's session or login hint.
89
+ const origin = new WeakMap<Request, IncomingMessage>();
90
+ const nodeRequestFor = (request: Request): IncomingMessage => {
91
+ const message = origin.get(request);
92
+ if (!message) throw new Error("@withone/connect/node: unknown request");
93
+ return message;
94
+ };
95
+ const loginHintFor = options.loginHintFor;
96
+ const routes = createOneConnectRoutes(oneConnect, {
97
+ identifyUser: (request) => options.identifyUser(nodeRequestFor(request)),
98
+ loginHintFor: loginHintFor
99
+ ? (request) => loginHintFor(nodeRequestFor(request))
89
100
  : undefined,
90
101
  signInUrl: options.signInUrl,
91
- };
92
- const routes = createOneConnectRoutes(oneConnect, routeOptions);
102
+ onComplete: options.onComplete,
103
+ } satisfies OneConnectRoutesOptions);
93
104
 
94
105
  const handle = async (
95
106
  request: IncomingMessage,
96
107
  response: ServerResponse,
97
108
  ): Promise<void> => {
98
- current = request;
99
- try {
100
- await send(response, await routes.GET(toWebRequest(request)));
101
- } finally {
102
- current = null;
103
- }
109
+ const web = toWebRequest(request);
110
+ origin.set(web, request);
111
+ await send(response, await routes.GET(web));
104
112
  };
105
113
 
106
114
  return { authorize: handle, callback: handle };
@@ -121,7 +121,7 @@ export interface OneConnectBaseConfig {
121
121
  export interface OneConnectKeyConfig extends OneConnectBaseConfig {
122
122
  mode?: "key";
123
123
  /** The app's connect key, minted on the app's page in the dashboard.
124
- * Server only. One key per environment. */
124
+ * Server only. Connect keys work in Production. */
125
125
  connectKey: string;
126
126
  userStore: OneConnectUserStore;
127
127
  tokenStore?: never;
package/src/types.ts CHANGED
@@ -75,9 +75,9 @@ export interface ConnectButtonLogo {
75
75
  imageUrl: string;
76
76
  }
77
77
 
78
- /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in the next minor. */
78
+ /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.17.0. */
79
79
  export type ConnectButtonPlatformInput = ConnectButtonLogoInput;
80
- /** @deprecated Renamed to `ConnectButtonLogo`; removed in the next minor. */
80
+ /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.17.0. */
81
81
  export type ConnectButtonPlatform = ConnectButtonLogo;
82
82
 
83
83
  export type ConnectButtonVariant = "default" | "accent" | "block";
@@ -94,7 +94,7 @@ export interface ConnectButtonProps {
94
94
  * into a "+N" chip. Decoration only; the permission set decides what
95
95
  * One asks for. */
96
96
  logos?: ConnectButtonLogoInput[];
97
- /** @deprecated Renamed to `logos`; removed in the next minor. */
97
+ /** @deprecated Renamed to `logos`; removed in 0.17.0. */
98
98
  platforms?: ConnectButtonLogoInput[];
99
99
  /** Whether this user has a live grant, from your server. When set, it
100
100
  * decides the Connected state. When omitted, the button shows