@knpkv/jira-cli 1.3.1 → 1.4.1

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +5 -5
  3. package/dist/JiraAuth.d.ts +23 -8
  4. package/dist/JiraAuth.d.ts.map +1 -1
  5. package/dist/JiraAuth.js +8 -303
  6. package/dist/JiraAuth.js.map +1 -1
  7. package/dist/bin.js +1 -1
  8. package/dist/bin.js.map +1 -1
  9. package/dist/commands/auth.d.ts +2 -2
  10. package/dist/commands/auth.d.ts.map +1 -1
  11. package/dist/commands/auth.js +8 -8
  12. package/dist/commands/auth.js.map +1 -1
  13. package/dist/commands/edit.d.ts +10 -1
  14. package/dist/commands/edit.d.ts.map +1 -1
  15. package/dist/commands/edit.js +9 -9
  16. package/dist/commands/edit.js.map +1 -1
  17. package/dist/commands/get.d.ts +6 -1
  18. package/dist/commands/get.d.ts.map +1 -1
  19. package/dist/commands/get.js +3 -3
  20. package/dist/commands/get.js.map +1 -1
  21. package/dist/commands/issue.d.ts +1 -1
  22. package/dist/commands/issue.d.ts.map +1 -1
  23. package/dist/commands/issue.js +1 -1
  24. package/dist/commands/issue.js.map +1 -1
  25. package/dist/commands/issueAttachment.d.ts +1 -1
  26. package/dist/commands/issueAttachment.d.ts.map +1 -1
  27. package/dist/commands/issueAttachment.js +13 -11
  28. package/dist/commands/issueAttachment.js.map +1 -1
  29. package/dist/commands/layers.d.ts +2 -2
  30. package/dist/commands/layers.d.ts.map +1 -1
  31. package/dist/commands/search.d.ts +6 -1
  32. package/dist/commands/search.d.ts.map +1 -1
  33. package/dist/commands/search.js +7 -7
  34. package/dist/commands/search.js.map +1 -1
  35. package/dist/commands/version.d.ts +9 -1
  36. package/dist/commands/version.d.ts.map +1 -1
  37. package/dist/commands/version.js +23 -23
  38. package/dist/commands/version.js.map +1 -1
  39. package/package.json +10 -10
  40. package/skills/jira/SKILL.md +4 -4
  41. package/src/JiraAuth.ts +24 -477
  42. package/src/bin.ts +1 -1
  43. package/src/commands/auth.ts +8 -8
  44. package/src/commands/edit.ts +9 -9
  45. package/src/commands/get.ts +3 -3
  46. package/src/commands/issue.ts +1 -1
  47. package/src/commands/issueAttachment.ts +21 -20
  48. package/src/commands/search.ts +7 -7
  49. package/src/commands/version.ts +26 -29
  50. package/test/IssueService.test.ts +2 -2
  51. package/test/JiraAuth.test.ts +79 -0
  52. package/test/VersionService.test.ts +2 -2
  53. package/test/commandTree.test.ts +1 -1
  54. package/test/integration.test.ts +7 -7
  55. package/test/remoteWrites.test.ts +3 -3
  56. package/vitest.config.integration.ts +4 -1
  57. package/vitest.config.ts +4 -1
  58. package/dist/internal/NodeLayers.d.ts +0 -7
  59. package/dist/internal/NodeLayers.d.ts.map +0 -1
  60. package/dist/internal/NodeLayers.js +0 -15
  61. package/dist/internal/NodeLayers.js.map +0 -1
  62. package/dist/internal/oauthServer.d.ts +0 -72
  63. package/dist/internal/oauthServer.d.ts.map +0 -1
  64. package/dist/internal/oauthServer.js +0 -96
  65. package/dist/internal/oauthServer.js.map +0 -1
  66. package/dist/internal/openBrowser.d.ts +0 -10
  67. package/dist/internal/openBrowser.d.ts.map +0 -1
  68. package/dist/internal/openBrowser.js +0 -17
  69. package/dist/internal/openBrowser.js.map +0 -1
  70. package/src/internal/NodeLayers.ts +0 -17
  71. package/src/internal/oauthServer.ts +0 -186
  72. package/src/internal/openBrowser.ts +0 -31
  73. package/test/JiraAuthRefresh.test.ts +0 -269
  74. package/test/oauthServer.test.ts +0 -171
@@ -1,72 +0,0 @@
1
- /**
2
- * Local HTTP callback server for OAuth2 authorization code capture.
3
- *
4
- * **Mental model**
5
- *
6
- * - **Scope-owned lifecycle**: {@link startCallbackServer} returns a `codePromise`
7
- * (Deferred). The server validates the CSRF `state` parameter, resolves the
8
- * Deferred with the authorization code, and stops when its enclosing scope closes.
9
- * - **Port auto-discovery**: Tries default port 8585, increments on conflict.
10
- *
11
- * @internal
12
- */
13
- import { OAuthError } from "@knpkv/atlassian-common/auth";
14
- import * as Context from "effect/Context";
15
- import * as Effect from "effect/Effect";
16
- import * as Layer from "effect/Layer";
17
- import type * as Scope from "effect/Scope";
18
- import { HttpServer } from "effect/unstable/http";
19
- import type * as HttpServerError from "effect/unstable/http/HttpServerError";
20
- /**
21
- * Factory service for creating HTTP servers.
22
- * This allows mocking the server creation in tests.
23
- *
24
- * @category Services
25
- */
26
- export interface HttpServerFactory {
27
- readonly createServerLayer: (options: CallbackServerListenOptions) => Layer.Layer<HttpServer.HttpServer, HttpServerError.ServeError, never>;
28
- }
29
- export interface CallbackServerListenOptions {
30
- readonly host: "localhost";
31
- readonly port: number;
32
- }
33
- export declare const callbackServerListenOptions: (port: number) => CallbackServerListenOptions;
34
- export declare const callbackUrl: (port: number) => string;
35
- declare const HttpServerFactoryTag_base: Context.ServiceClass<HttpServerFactoryTag, "@knpkv/jira-cli/HttpServerFactory", HttpServerFactory>;
36
- /**
37
- * Tag for the HttpServerFactory service.
38
- *
39
- * @category Services
40
- */
41
- export declare class HttpServerFactoryTag extends HttpServerFactoryTag_base {
42
- }
43
- /**
44
- * Create a HttpServerFactory layer from a layer factory function.
45
- * This allows injecting platform-specific implementations.
46
- *
47
- * @param createLayerFn - Function that creates HttpServer layer for a given port
48
- * @returns Layer providing HttpServerFactory
49
- *
50
- * @category Layers
51
- */
52
- export declare const makeHttpServerFactory: (createLayerFn: (options: CallbackServerListenOptions) => Layer.Layer<HttpServer.HttpServer, HttpServerError.ServeError, never>) => Layer.Layer<HttpServerFactoryTag>;
53
- /**
54
- * Result from the OAuth callback server.
55
- */
56
- export interface CallbackServerResult {
57
- /** Promise that resolves with the authorization code */
58
- readonly codePromise: Effect.Effect<string, OAuthError>;
59
- /** The port the server is listening on */
60
- readonly port: number;
61
- }
62
- /**
63
- * Start a local HTTP server to receive OAuth callback.
64
- *
65
- * @param expectedState - The state parameter to verify against CSRF
66
- * @returns Server control interface with code promise and port
67
- *
68
- * @category OAuth
69
- */
70
- export declare const startCallbackServer: (expectedState: string) => Effect.Effect<CallbackServerResult, OAuthError, HttpServerFactoryTag | Scope.Scope>;
71
- export {};
72
- //# sourceMappingURL=oauthServer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"oauthServer.d.ts","sourceRoot":"","sources":["../../src/internal/oauthServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAc,UAAU,EAAsB,MAAM,sBAAsB,CAAA;AACjF,OAAO,KAAK,KAAK,eAAe,MAAM,sCAAsC,CAAA;AAK5E;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,KAAK,CAAC,KAAK,CAC/E,UAAU,CAAC,UAAU,EACrB,eAAe,CAAC,UAAU,EAC1B,KAAK,CACN,CAAA;CACF;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,2BAA2B,SAAU,MAAM,KAAG,2BAGzD,CAAA;AAEF,eAAO,MAAM,WAAW,SAAU,MAAM,KAAG,MAA6C,CAAA;;AAExF;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,yBAGF;CAAG;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,kBACjB,CACb,OAAO,EAAE,2BAA2B,KACjC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,KACzE,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAG/B,CAAA;AAEJ;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACvD,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAQD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,kBACf,MAAM,KACpB,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,UAAU,EAAE,oBAAoB,GAAG,KAAK,CAAC,KAAK,CAgFjF,CAAA"}
@@ -1,96 +0,0 @@
1
- /**
2
- * Local HTTP callback server for OAuth2 authorization code capture.
3
- *
4
- * **Mental model**
5
- *
6
- * - **Scope-owned lifecycle**: {@link startCallbackServer} returns a `codePromise`
7
- * (Deferred). The server validates the CSRF `state` parameter, resolves the
8
- * Deferred with the authorization code, and stops when its enclosing scope closes.
9
- * - **Port auto-discovery**: Tries default port 8585, increments on conflict.
10
- *
11
- * @internal
12
- */
13
- import { OAuthError } from "@knpkv/atlassian-common/auth";
14
- import * as Context from "effect/Context";
15
- import * as Deferred from "effect/Deferred";
16
- import * as Effect from "effect/Effect";
17
- import * as Layer from "effect/Layer";
18
- import * as Schema from "effect/Schema";
19
- import { HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http";
20
- const DEFAULT_PORT = 8585;
21
- const MAX_PORT = 8594;
22
- export const callbackServerListenOptions = (port) => ({
23
- host: "localhost",
24
- port
25
- });
26
- export const callbackUrl = (port) => `http://localhost:${port}/callback`;
27
- /**
28
- * Tag for the HttpServerFactory service.
29
- *
30
- * @category Services
31
- */
32
- export class HttpServerFactoryTag extends Context.Service()("@knpkv/jira-cli/HttpServerFactory") {
33
- }
34
- /**
35
- * Create a HttpServerFactory layer from a layer factory function.
36
- * This allows injecting platform-specific implementations.
37
- *
38
- * @param createLayerFn - Function that creates HttpServer layer for a given port
39
- * @returns Layer providing HttpServerFactory
40
- *
41
- * @category Layers
42
- */
43
- export const makeHttpServerFactory = (createLayerFn) => Layer.succeed(HttpServerFactoryTag, {
44
- createServerLayer: createLayerFn
45
- });
46
- const AddressInUseCause = Schema.Struct({
47
- code: Schema.Literal("EADDRINUSE")
48
- });
49
- const isAddressInUse = (error) => Schema.is(AddressInUseCause)(error.cause);
50
- /**
51
- * Start a local HTTP server to receive OAuth callback.
52
- *
53
- * @param expectedState - The state parameter to verify against CSRF
54
- * @returns Server control interface with code promise and port
55
- *
56
- * @category OAuth
57
- */
58
- export const startCallbackServer = (expectedState) => Effect.gen(function* () {
59
- const factory = yield* HttpServerFactoryTag;
60
- const deferred = yield* Deferred.make();
61
- const serverScope = yield* Effect.scope;
62
- const buildServerContext = (port) => Layer.buildWithScope(factory.createServerLayer(callbackServerListenOptions(port)), serverScope).pipe(Effect.map((context) => ({ context, port })), Effect.catchIf((error) => isAddressInUse(error) && port < MAX_PORT, () => buildServerContext(port + 1)), Effect.mapError((cause) => new OAuthError({ step: "authorize", cause })));
63
- const { context: serverContext } = yield* buildServerContext(DEFAULT_PORT);
64
- const server = Context.get(serverContext, HttpServer.HttpServer);
65
- const port = yield* (server.address._tag === "TcpAddress"
66
- ? Effect.succeed(server.address.port)
67
- : Effect.fail(new OAuthError({ step: "authorize", cause: "OAuth callback server must listen on a TCP port" })));
68
- const router = yield* HttpRouter.make;
69
- yield* router.add("GET", "/callback", (req) => Effect.gen(function* () {
70
- const url = new URL(req.url, callbackUrl(port));
71
- const code = url.searchParams.get("code");
72
- const state = url.searchParams.get("state");
73
- const error = url.searchParams.get("error");
74
- const errorDescription = url.searchParams.get("error_description");
75
- if (state !== expectedState) {
76
- return HttpServerResponse.html("<html><body><h1>Security Error</h1><p>State verification failed.</p></body></html>").pipe(HttpServerResponse.setStatus(403));
77
- }
78
- if (error) {
79
- yield* Deferred.fail(deferred, new OAuthError({ step: "authorize", cause: errorDescription ?? error }));
80
- return HttpServerResponse.html("<html><body><h1>Authorization Failed</h1><p>You can close this window.</p></body></html>");
81
- }
82
- if (!code) {
83
- yield* Deferred.fail(deferred, new OAuthError({ step: "authorize", cause: "No authorization code received" }));
84
- return HttpServerResponse.html("<html><body><h1>Error</h1><p>No authorization code received.</p></body></html>");
85
- }
86
- yield* Deferred.succeed(deferred, code);
87
- return HttpServerResponse.html("<html><body><h1>Success!</h1><p>You can close this window and return to the terminal.</p></body></html>");
88
- }));
89
- const app = router.asHttpEffect();
90
- yield* HttpServer.serveEffect(app).pipe(Effect.provide(serverContext), Effect.forkScoped);
91
- return {
92
- codePromise: Deferred.await(deferred),
93
- port
94
- };
95
- });
96
- //# sourceMappingURL=oauthServer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"oauthServer.js","sourceRoot":"","sources":["../../src/internal/oauthServer.ts"],"names":[],"mappings":"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"}
@@ -1,10 +0,0 @@
1
- /**
2
- * Cross-platform browser launcher backed by Effect v4 child process services.
3
- *
4
- * @internal
5
- */
6
- import * as Effect from "effect/Effect";
7
- import type * as PlatformError from "effect/PlatformError";
8
- import { ChildProcessSpawner } from "effect/unstable/process";
9
- export declare const openBrowser: (url: string) => Effect.Effect<void, PlatformError.PlatformError, ChildProcessSpawner.ChildProcessSpawner>;
10
- //# sourceMappingURL=openBrowser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"openBrowser.d.ts","sourceRoot":"","sources":["../../src/internal/openBrowser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,aAAa,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAiB3E,eAAO,MAAM,WAAW,QACjB,MAAM,KACV,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,aAAa,EAAE,mBAAmB,CAAC,mBAAmB,CAIxF,CAAA"}
@@ -1,17 +0,0 @@
1
- /**
2
- * Cross-platform browser launcher backed by Effect v4 child process services.
3
- *
4
- * @internal
5
- */
6
- import * as Effect from "effect/Effect";
7
- import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
8
- const run = (command, args) => Effect.gen(function* () {
9
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
10
- yield* spawner.exitCode(ChildProcess.make(command, args, {
11
- stdin: "ignore",
12
- stdout: "ignore",
13
- stderr: "ignore"
14
- }));
15
- });
16
- export const openBrowser = (url) => run("open", [url]).pipe(Effect.catch(() => run("xdg-open", [url])), Effect.catch(() => run("rundll32.exe", ["url.dll,FileProtocolHandler", url])));
17
- //# sourceMappingURL=openBrowser.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"openBrowser.js","sourceRoot":"","sources":["../../src/internal/openBrowser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAE3E,MAAM,GAAG,GAAG,CACV,OAAe,EACf,IAA2B,EACgE,EAAE,CAC7F,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,mBAAmB,CAAA;IAC9D,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CACrB,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE;QAC/B,KAAK,EAAE,QAAQ;QACf,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,QAAQ;KACjB,CAAC,CACH,CAAA;AACH,CAAC,CAAC,CAAA;AAEJ,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,GAAW,EACgF,EAAE,CAC7F,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CACrB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAC1C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC,CAAC,CAC9E,CAAA"}
@@ -1,17 +0,0 @@
1
- /**
2
- * Node.js-specific HTTP server factory — the only file importing `@effect/platform-node` server.
3
- *
4
- * @internal
5
- */
6
- import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"
7
- import { createServer } from "node:http"
8
- import { makeHttpServerFactory } from "./oauthServer.js"
9
-
10
- /**
11
- * HTTP Server factory layer using Node.js http module.
12
- *
13
- * @category Layers
14
- */
15
- export const HttpServerFactoryLive = makeHttpServerFactory(
16
- (options) => NodeHttpServer.layerServer(createServer, options)
17
- )
@@ -1,186 +0,0 @@
1
- /**
2
- * Local HTTP callback server for OAuth2 authorization code capture.
3
- *
4
- * **Mental model**
5
- *
6
- * - **Scope-owned lifecycle**: {@link startCallbackServer} returns a `codePromise`
7
- * (Deferred). The server validates the CSRF `state` parameter, resolves the
8
- * Deferred with the authorization code, and stops when its enclosing scope closes.
9
- * - **Port auto-discovery**: Tries default port 8585, increments on conflict.
10
- *
11
- * @internal
12
- */
13
- import { OAuthError } from "@knpkv/atlassian-common/auth"
14
- import * as Context from "effect/Context"
15
- import * as Deferred from "effect/Deferred"
16
- import * as Effect from "effect/Effect"
17
- import * as Layer from "effect/Layer"
18
- import * as Schema from "effect/Schema"
19
- import type * as Scope from "effect/Scope"
20
- import { HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
21
- import type * as HttpServerError from "effect/unstable/http/HttpServerError"
22
-
23
- const DEFAULT_PORT = 8585
24
- const MAX_PORT = 8594
25
- type HttpServerInstance = Effect.Success<typeof HttpServer.HttpServer>
26
- /**
27
- * Factory service for creating HTTP servers.
28
- * This allows mocking the server creation in tests.
29
- *
30
- * @category Services
31
- */
32
- export interface HttpServerFactory {
33
- readonly createServerLayer: (options: CallbackServerListenOptions) => Layer.Layer<
34
- HttpServer.HttpServer,
35
- HttpServerError.ServeError,
36
- never
37
- >
38
- }
39
-
40
- export interface CallbackServerListenOptions {
41
- readonly host: "localhost"
42
- readonly port: number
43
- }
44
-
45
- export const callbackServerListenOptions = (port: number): CallbackServerListenOptions => ({
46
- host: "localhost",
47
- port
48
- })
49
-
50
- export const callbackUrl = (port: number): string => `http://localhost:${port}/callback`
51
-
52
- /**
53
- * Tag for the HttpServerFactory service.
54
- *
55
- * @category Services
56
- */
57
- export class HttpServerFactoryTag extends Context.Service<
58
- HttpServerFactoryTag,
59
- HttpServerFactory
60
- >()("@knpkv/jira-cli/HttpServerFactory") {}
61
-
62
- /**
63
- * Create a HttpServerFactory layer from a layer factory function.
64
- * This allows injecting platform-specific implementations.
65
- *
66
- * @param createLayerFn - Function that creates HttpServer layer for a given port
67
- * @returns Layer providing HttpServerFactory
68
- *
69
- * @category Layers
70
- */
71
- export const makeHttpServerFactory = (
72
- createLayerFn: (
73
- options: CallbackServerListenOptions
74
- ) => Layer.Layer<HttpServer.HttpServer, HttpServerError.ServeError, never>
75
- ): Layer.Layer<HttpServerFactoryTag> =>
76
- Layer.succeed(HttpServerFactoryTag, {
77
- createServerLayer: createLayerFn
78
- })
79
-
80
- /**
81
- * Result from the OAuth callback server.
82
- */
83
- export interface CallbackServerResult {
84
- /** Promise that resolves with the authorization code */
85
- readonly codePromise: Effect.Effect<string, OAuthError>
86
- /** The port the server is listening on */
87
- readonly port: number
88
- }
89
-
90
- const AddressInUseCause = Schema.Struct({
91
- code: Schema.Literal("EADDRINUSE")
92
- })
93
-
94
- const isAddressInUse = (error: HttpServerError.ServeError): boolean => Schema.is(AddressInUseCause)(error.cause)
95
-
96
- /**
97
- * Start a local HTTP server to receive OAuth callback.
98
- *
99
- * @param expectedState - The state parameter to verify against CSRF
100
- * @returns Server control interface with code promise and port
101
- *
102
- * @category OAuth
103
- */
104
- export const startCallbackServer = (
105
- expectedState: string
106
- ): Effect.Effect<CallbackServerResult, OAuthError, HttpServerFactoryTag | Scope.Scope> =>
107
- Effect.gen(function*() {
108
- const factory = yield* HttpServerFactoryTag
109
- const deferred = yield* Deferred.make<string, OAuthError>()
110
- const serverScope = yield* Effect.scope
111
- const buildServerContext = (port: number): Effect.Effect<
112
- { readonly context: Context.Context<HttpServer.HttpServer>; readonly port: number },
113
- OAuthError
114
- > =>
115
- Layer.buildWithScope(factory.createServerLayer(callbackServerListenOptions(port)), serverScope).pipe(
116
- Effect.map((context) => ({ context, port })),
117
- Effect.catchIf(
118
- (error) => isAddressInUse(error) && port < MAX_PORT,
119
- () => buildServerContext(port + 1)
120
- ),
121
- Effect.mapError((cause) => new OAuthError({ step: "authorize", cause }))
122
- )
123
- const { context: serverContext } = yield* buildServerContext(DEFAULT_PORT)
124
- const server: HttpServerInstance = Context.get(serverContext, HttpServer.HttpServer)
125
- const port = yield* (server.address._tag === "TcpAddress"
126
- ? Effect.succeed(server.address.port)
127
- : Effect.fail(new OAuthError({ step: "authorize", cause: "OAuth callback server must listen on a TCP port" })))
128
-
129
- const router = yield* HttpRouter.make
130
- yield* router.add(
131
- "GET",
132
- "/callback",
133
- (req) =>
134
- Effect.gen(function*() {
135
- const url = new URL(req.url, callbackUrl(port))
136
- const code = url.searchParams.get("code")
137
- const state = url.searchParams.get("state")
138
- const error = url.searchParams.get("error")
139
- const errorDescription = url.searchParams.get("error_description")
140
-
141
- if (state !== expectedState) {
142
- return HttpServerResponse.html(
143
- "<html><body><h1>Security Error</h1><p>State verification failed.</p></body></html>"
144
- ).pipe(
145
- HttpServerResponse.setStatus(403)
146
- )
147
- }
148
-
149
- if (error) {
150
- yield* Deferred.fail(
151
- deferred,
152
- new OAuthError({ step: "authorize", cause: errorDescription ?? error })
153
- )
154
- return HttpServerResponse.html(
155
- "<html><body><h1>Authorization Failed</h1><p>You can close this window.</p></body></html>"
156
- )
157
- }
158
-
159
- if (!code) {
160
- yield* Deferred.fail(
161
- deferred,
162
- new OAuthError({ step: "authorize", cause: "No authorization code received" })
163
- )
164
- return HttpServerResponse.html(
165
- "<html><body><h1>Error</h1><p>No authorization code received.</p></body></html>"
166
- )
167
- }
168
-
169
- yield* Deferred.succeed(deferred, code)
170
- return HttpServerResponse.html(
171
- "<html><body><h1>Success!</h1><p>You can close this window and return to the terminal.</p></body></html>"
172
- )
173
- })
174
- )
175
- const app = router.asHttpEffect()
176
-
177
- yield* HttpServer.serveEffect(app).pipe(
178
- Effect.provide(serverContext),
179
- Effect.forkScoped
180
- )
181
-
182
- return {
183
- codePromise: Deferred.await(deferred),
184
- port
185
- }
186
- })
@@ -1,31 +0,0 @@
1
- /**
2
- * Cross-platform browser launcher backed by Effect v4 child process services.
3
- *
4
- * @internal
5
- */
6
- import * as Effect from "effect/Effect"
7
- import type * as PlatformError from "effect/PlatformError"
8
- import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
9
-
10
- const run = (
11
- command: string,
12
- args: ReadonlyArray<string>
13
- ): Effect.Effect<void, PlatformError.PlatformError, ChildProcessSpawner.ChildProcessSpawner> =>
14
- Effect.gen(function*() {
15
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
16
- yield* spawner.exitCode(
17
- ChildProcess.make(command, args, {
18
- stdin: "ignore",
19
- stdout: "ignore",
20
- stderr: "ignore"
21
- })
22
- )
23
- })
24
-
25
- export const openBrowser = (
26
- url: string
27
- ): Effect.Effect<void, PlatformError.PlatformError, ChildProcessSpawner.ChildProcessSpawner> =>
28
- run("open", [url]).pipe(
29
- Effect.catch(() => run("xdg-open", [url])),
30
- Effect.catch(() => run("rundll32.exe", ["url.dll,FileProtocolHandler", url]))
31
- )