History

Merge remote-tracking branch 'origin/codex/batch16-react-oauth-popup-exit' into codex/effect-lint-integration

Rhys Sullivan committed 6 months ago

Commit 27459402 parents e5b1196 23439e61 file · +80 −66Browse files

Unified · Split

packages/react/src/plugins/oauth-sign-in.tsx+80−66
@@ -1,5 +1,7 @@
1import { useCallback, useEffect, useRef, useState } from "react";1import { useCallback, useEffect, useRef, useState } from "react";
2import { useAtomSet } from "@effect/atom-react";2import { useAtomSet } from "@effect/atom-react";
3import * as Effect from "effect/Effect";
4import * as Exit from "effect/Exit";
3 5
4import { cancelOAuth, startOAuth } from "../api/atoms";6import { cancelOAuth, startOAuth } from "../api/atoms";
5import { openOAuthPopup, type OAuthPopupResult } from "../api/oauth-popup";7import { openOAuthPopup, type OAuthPopupResult } from "../api/oauth-popup";
@@ -80,8 +82,8 @@ export function useOAuthPopupFlow< · 74 unmodified lines
80 startErrorMessage,82 startErrorMessage,
81 } = options;83 } = options;
82 const scopeId = useScope();84 const scopeId = useScope();
83 const doStartOAuth = useAtomSet(startOAuth, { mode: "promise" });85 const doStartOAuth = useAtomSet(startOAuth, { mode: "promiseExit" });
84 const doCancelOAuth = useAtomSet(cancelOAuth, { mode: "promise" });86 const doCancelOAuth = useAtomSet(cancelOAuth, { mode: "promiseExit" });
85 const [busy, setBusy] = useState(false);87 const [busy, setBusy] = useState(false);
86 const [error, setError] = useState<string | null>(null);88 const [error, setError] = useState<string | null>(null);
87 const cleanupRef = useRef<(() => void) | null>(null);89 const cleanupRef = useRef<(() => void) | null>(null);
@@ -92,7 +94,7 @@ export function useOAuthPopupFlow< · 4 unmodified lines
92 void doCancelOAuth({94 void doCancelOAuth({
93 params: { scopeId },95 params: { scopeId },
94 payload: { sessionId },96 payload: { sessionId },
95 }).catch(() => undefined);97 });
96 },98 },
97 [doCancelOAuth, scopeId],99 [doCancelOAuth, scopeId],
98 );100 );
@@ -122,73 +124,81 @@ export function useOAuthPopupFlow< · 23 unmodified lines
122 cancel();124 cancel();
123 setBusy(true);125 setBusy(true);
124 setError(null);126 setError(null);
125 try {127 const startExit = await Effect.runPromiseExit(
126 const response = await input.run();128 Effect.tryPromise({
127 if (response.authorizationUrl === null) {129 try: input.run,
128 const message =130 catch: (cause) => cause,
129 noAuthorizationUrlMessage ?? "OAuth start did not produce an authorization URL";131 }),
130 setBusy(false);132 );
131 setError(message);133 if (Exit.isFailure(startExit)) {
132 input.onError?.(message);134 const message = startErrorMessage ?? "Failed to start sign-in";
133 return;135 setBusy(false);
134 }136 setError(message);
135 137 input.onError?.(message);
136 sessionRef.current = response.sessionId;138 return;
137 input.onAuthorizationStarted?.(response);139 }
138 cleanupRef.current = openOAuthPopup<TPayload>({140 const response = startExit.value;
139 url: response.authorizationUrl,141 if (response.authorizationUrl === null) {
140 popupName,142 const message =
141 channelName: OAUTH_POPUP_MESSAGE_TYPE,143 noAuthorizationUrlMessage ?? "OAuth start did not produce an authorization URL";
142 expectedSessionId: response.sessionId,144 setBusy(false);
143 onResult: async (result: OAuthPopupResult<TPayload>) => {145 setError(message);
144 cleanupRef.current = null;146 input.onError?.(message);
145 sessionRef.current = null;147 return;
148 }
146 149
147 if (!result.ok) {150 sessionRef.current = response.sessionId;
148 setBusy(false);151 input.onAuthorizationStarted?.(response);
149 setError(result.error);152 cleanupRef.current = openOAuthPopup<TPayload>({
150 input.onError?.(result.error);153 url: response.authorizationUrl,
151 return;154 popupName,
152 }155 channelName: OAUTH_POPUP_MESSAGE_TYPE,
156 expectedSessionId: response.sessionId,
157 onResult: async (result: OAuthPopupResult<TPayload>) => {
158 cleanupRef.current = null;
159 sessionRef.current = null;
153 160
154 try {161 if (!result.ok) {
155 await input.onSuccess(result);
156 setBusy(false);
157 } catch (e) {
158 const message = e instanceof Error ? e.message : "Failed to persist new connection";
159 setBusy(false);
160 setError(message);
161 input.onError?.(message);
162 }
163 },
164 onClosed: () => {
165 cleanupRef.current = null;
166 sessionRef.current = null;
167 cancelSession(response.sessionId);
168 const message =
169 popupClosedMessage ??
170 "Sign-in cancelled - popup was closed before completing the flow.";
171 setBusy(false);162 setBusy(false);
172 setError(message);163 setError(result.error);
173 input.onError?.(message);164 input.onError?.(result.error);
174 },165 return;
175 onOpenFailed: () => {166 }
176 cleanupRef.current = null;167
177 sessionRef.current = null;168 const persisted = await Promise.resolve(input.onSuccess(result)).then(
178 cancelSession(response.sessionId);169 () => true,
179 const message = popupBlockedMessage ?? "Sign-in popup was blocked by the browser";170 () => false,
171 );
172 if (!persisted) {
173 const message = "Failed to persist new connection";
180 setBusy(false);174 setBusy(false);
181 setError(message);175 setError(message);
182 input.onError?.(message);176 input.onError?.(message);
183 },177 return;
184 });178 }
185 } catch (e) {179 setBusy(false);
186 const message =180 },
187 e instanceof Error ? e.message : (startErrorMessage ?? "Failed to start sign-in");181 onClosed: () => {
188 setBusy(false);182 cleanupRef.current = null;
189 setError(message);183 sessionRef.current = null;
190 input.onError?.(message);184 cancelSession(response.sessionId);
191 }185 const message =
186 popupClosedMessage ??
187 "Sign-in cancelled - popup was closed before completing the flow.";
188 setBusy(false);
189 setError(message);
190 input.onError?.(message);
191 },
192 onOpenFailed: () => {
193 cleanupRef.current = null;
194 sessionRef.current = null;
195 cancelSession(response.sessionId);
196 const message = popupBlockedMessage ?? "Sign-in popup was blocked by the browser";
197 setBusy(false);
198 setError(message);
199 input.onError?.(message);
200 },
201 });
192 },202 },
193 [203 [
194 cancel,204 cancel,
@@ -214,10 +224,14 @@ export function useOAuthPopupFlow< · 19 unmodified lines
214 ...input.payload,224 ...input.payload,
215 redirectUrl: input.payload.redirectUrl ?? oauthCallbackUrl(callbackPath),225 redirectUrl: input.payload.redirectUrl ?? oauthCallbackUrl(callbackPath),
216 },226 },
217 }),227 }).then((exit) =>
228 Exit.isSuccess(exit)
229 ? exit.value
230 : Effect.runPromise(Effect.fail(startErrorMessage ?? "Failed to start sign-in")),
231 ),
218 });232 });
219 },233 },
220 [callbackPath, doStartOAuth, openAuthorization, scopeId],234 [callbackPath, doStartOAuth, openAuthorization, scopeId, startErrorMessage],
221 );235 );
222 236
223 return {237 return {