( connection: ExecutorServerConnection, )
| 756 | const OAUTH_REFRESH_SKEW_SECONDS = 60; |
| 757 | |
| 758 | const refreshOAuthConnection = ( |
| 759 | connection: ExecutorServerConnection, |
| 760 | ): Effect.Effect<ExecutorServerConnection, Error, FileSystem.FileSystem | PlatformPath.Path> => |
| 761 | Effect.gen(function* () { |
| 762 | const auth = connection.auth; |
| 763 | if (!auth || auth.kind !== "oauth") return connection; |
| 764 | const now = Math.floor(Date.now() / 1000); |
| 765 | if (auth.expiresAt && auth.expiresAt - now > OAUTH_REFRESH_SKEW_SECONDS) return connection; |
| 766 | // Destructure so the narrowed string types survive into the deferred |
| 767 | // `tryPromise` callback (where TS would otherwise re-widen the fields). |
| 768 | const { refreshToken, tokenEndpoint, clientId } = auth; |
| 769 | if (!refreshToken || !tokenEndpoint || !clientId) return connection; |
| 770 | |
| 771 | const headers = yield* resolveExecutorServerConfiguredHeaders(connection, process.env).pipe( |
| 772 | Effect.mapError(toError), |
| 773 | ); |
| 774 | |
| 775 | const refreshed = yield* Effect.tryPromise({ |
| 776 | try: () => |
| 777 | refreshDeviceTokens({ |
| 778 | tokenEndpoint, |
| 779 | clientId, |
| 780 | refreshToken, |
| 781 | serverOrigin: connection.origin, |
| 782 | headers, |
| 783 | }), |
| 784 | catch: toError, |
| 785 | // On a failed refresh, keep the existing token and let the eventual 401 |
| 786 | // surface, better than blocking the command on a transient hiccup. |
| 787 | }).pipe(Effect.option); |
| 788 | if (Option.isNone(refreshed)) return connection; |
| 789 | |
| 790 | const next = refreshed.value; |
| 791 | const nextConnection = normalizeExecutorServerConnection({ |
| 792 | ...connection, |
| 793 | auth: { |
| 794 | kind: "oauth", |
| 795 | accessToken: next.accessToken, |
| 796 | refreshToken: next.refreshToken ?? refreshToken, |
| 797 | ...(next.expiresAt ? { expiresAt: next.expiresAt } : {}), |
| 798 | tokenEndpoint, |
| 799 | clientId, |
| 800 | }, |
| 801 | }); |
| 802 | |
| 803 | const profileName = profileNameFromConnectionKey(connection.key); |
| 804 | if (profileName) { |
| 805 | yield* upsertCliServerConnectionProfile({ |
| 806 | name: profileName, |
| 807 | connection: nextConnection, |
| 808 | makeDefault: false, |
| 809 | }).pipe(Effect.ignore); |
| 810 | } |
| 811 | return nextConnection; |
| 812 | }); |
| 813 | |
| 814 | const resolveExecutorServerConnection = ( |
| 815 | target: ServerTarget, |
no test coverage detected