@corespec/vite-plugin-api-auto-login 0.1.0 → 0.2.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.
Files changed (33) hide show
  1. package/README.md +88 -69
  2. package/dist/auth/AuthRequest.d.ts +1 -3
  3. package/dist/auth/AuthRequest.d.ts.map +1 -1
  4. package/dist/auth/createFormDataRequest.d.ts +4 -0
  5. package/dist/auth/createFormDataRequest.d.ts.map +1 -0
  6. package/dist/auth/createTokenStream.d.ts +14 -0
  7. package/dist/auth/createTokenStream.d.ts.map +1 -0
  8. package/dist/config/loadPluginConfig.d.ts +5 -0
  9. package/dist/config/loadPluginConfig.d.ts.map +1 -0
  10. package/dist/config/types.d.ts +9 -8
  11. package/dist/config/types.d.ts.map +1 -1
  12. package/dist/http/isHtmlDocumentRequest.d.ts +3 -0
  13. package/dist/http/isHtmlDocumentRequest.d.ts.map +1 -0
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/middleware/createAutoLoginMiddleware.d.ts +5 -0
  17. package/dist/middleware/createAutoLoginMiddleware.d.ts.map +1 -0
  18. package/dist/plugin.d.ts.map +1 -1
  19. package/dist/vite-plugin-api-auto-login.cjs +1 -1
  20. package/dist/vite-plugin-api-auto-login.js +144 -124
  21. package/package.json +3 -1
  22. package/dist/auth/AuthSession.d.ts +0 -24
  23. package/dist/auth/AuthSession.d.ts.map +0 -1
  24. package/dist/auth/FormDataAuthRequest.d.ts +0 -9
  25. package/dist/auth/FormDataAuthRequest.d.ts.map +0 -1
  26. package/dist/config/PluginConfig.d.ts +0 -14
  27. package/dist/config/PluginConfig.d.ts.map +0 -1
  28. package/dist/http/AuthResponse.d.ts +0 -7
  29. package/dist/http/AuthResponse.d.ts.map +0 -1
  30. package/dist/http/HtmlDocumentRequest.d.ts +0 -5
  31. package/dist/http/HtmlDocumentRequest.d.ts.map +0 -1
  32. package/dist/middleware/AutoLoginMiddleware.d.ts +0 -12
  33. package/dist/middleware/AutoLoginMiddleware.d.ts.map +0 -1
package/README.md CHANGED
@@ -46,80 +46,104 @@ Values still come from `API_AUTO_LOGIN_USERNAME` and `API_AUTO_LOGIN_PASSWORD`;
46
46
  only the outgoing form field names change. Names must be non-empty and distinct.
47
47
  The `User-Agent` header is unaffected.
48
48
 
49
- Use `formFields` only with the default strategy; it cannot be combined with
50
- `request` or `createAuthRequest`. When constructing the exported strategy directly,
51
- use `new FormDataAuthRequest(config, { username: 'email' })`.
52
- The `FormFieldNames` type is exported for reusable mappings.
49
+ Use `formFields` only with the default request; it cannot be combined with a custom
50
+ `request`. The exported `createFormDataRequest(config, fields)` function returns
51
+ a request callback for consumers who want to compose the default behavior.
53
52
 
54
53
  ## Customizing the login request
55
54
 
56
- Use `createAuthRequest(config)` to create a `AuthRequest` strategy for each
57
- Vite server initialization. The strategy controls the URL, HTTP method, headers,
58
- and body. A class can keep any additional configuration it needs:
55
+ Pass a function that receives the parsed configuration and returns the URL and
56
+ Undici fetch options. It can return a promise. Create a fresh request body each
57
+ time the callback runs:
59
58
 
60
59
  ```ts
61
60
  import { defineConfig } from 'vite'
62
- import {
63
- viteAPIAutoLogin,
64
- type PluginEnv,
65
- type AuthRequest,
66
- type AuthRequestOptions,
67
- } from '@corespec/vite-plugin-api-auto-login'
68
-
69
- class JsonAuthRequest implements AuthRequest {
70
- create(config: Readonly<PluginEnv>): AuthRequestOptions {
71
- if (!config.loginUrl || !config.username || !config.password) {
72
- throw new Error('JSON login requires an endpoint, username, and password')
73
- }
74
-
75
- return {
76
- url: config.loginUrl,
77
- init: {
78
- method: 'POST',
79
- headers: { 'content-type': 'application/json' },
80
- body: JSON.stringify({
81
- username: config.username,
82
- password: config.password,
83
- }),
84
- },
85
- }
86
- }
87
- }
61
+ import { viteAPIAutoLogin } from '@corespec/vite-plugin-api-auto-login'
88
62
 
89
63
  export default defineConfig({
90
- plugins: [viteAPIAutoLogin({ createAuthRequest: () => new JsonAuthRequest() })],
64
+ plugins: [
65
+ viteAPIAutoLogin({
66
+ request: (config) => {
67
+ if (!config.loginUrl || !config.username || !config.password) {
68
+ throw new Error('JSON login requires an endpoint, username, and password')
69
+ }
70
+ return {
71
+ url: config.loginUrl,
72
+ init: {
73
+ method: 'POST',
74
+ headers: { 'Content-Type': 'application/json' },
75
+ body: JSON.stringify({
76
+ email: config.username,
77
+ password: config.password,
78
+ }),
79
+ },
80
+ }
81
+ },
82
+ }),
83
+ ],
91
84
  })
92
85
  ```
93
86
 
94
- The factory receives the readonly parsed configuration and runs once per
95
- `configureServer` invocation, not once per request. Return a fresh instance to
96
- keep mutable strategy state separate between servers. Factory errors are logged
97
- and prevent middleware registration for that server. The factory is synchronous.
87
+ The callback runs lazily when a login is needed. Concurrent requests share one
88
+ login through RxJS, and each Vite server has its own token cache. JWTs are reused
89
+ until their `exp` timestamp; opaque tokens and tokens without readable expiry are
90
+ not cached between completed logins. Errors are logged and requests continue;
91
+ the next request can attempt authentication again. There are no automatic retries.
98
92
 
99
- Use `request: new JsonAuthRequest()` for
100
- shared strategy instances. Supply either `request` or `createAuthRequest`, never
101
- both; TypeScript and runtime validation enforce this.
93
+ Custom callbacks validate their own requirements and may omit the default login
94
+ variables. Without a callback, the default sends a multipart POST with the mapped
95
+ username/password fields and a `User-Agent` header.
102
96
 
103
- `create` can return a promise. It runs whenever a new token is needed, so create
104
- a fresh body for each call. Concurrent login requests are deduplicated, and
105
- valid JWT tokens are cached until their `exp` timestamp. Tokens without a
106
- readable JWT expiry are not cached.
97
+ `init` supports Undici options including `signal` and `redirect`. The plugin
98
+ combines your signal with its lifecycle cancellation and manages `dispatcher`
99
+ through the TLS configuration. The response must be successful JSON containing
100
+ a non-empty `token` string.
107
101
 
108
- Without this option, `FormDataAuthRequest` sends a multipart POST
109
- with `username` and `password` fields and a `User-Agent` header. This class is also exported
110
- for consumers that want to extend the default request.
102
+ ## Cookie options
111
103
 
112
- The `init` object supports Undici fetch options, including `signal` and
113
- `redirect`. The plugin manages `dispatcher` through its TLS configuration.
114
- The response must still be successful HTTP JSON containing a non-empty
115
- `token` string. Custom strategies validate their own authentication requirements and can omit
116
- all three default login variables.
104
+ ```ts
105
+ viteAPIAutoLogin({
106
+ cookie: {
107
+ path: '/api',
108
+ sameSite: 'strict',
109
+ secure: false,
110
+ httpOnly: true,
111
+ },
112
+ })
113
+ ```
114
+
115
+ Defaults preserve the original behavior: `path: '/'`, `sameSite: 'lax'`,
116
+ `secure: false`, and `httpOnly: false`. `sameSite` accepts `lax`, `strict`, or
117
+ `none`; `none` requires `secure: true`. Enable `httpOnly` when your frontend does
118
+ not need JavaScript access to the cookie. `secure` cookies require HTTPS in the
119
+ normal browser cookie model.
120
+
121
+ The name still comes from `API_AUTO_LOGIN_COOKIE_NAME`. Cookie `path` controls
122
+ where the browser sends the cookie, not which page requests trigger login.
123
+ Cookies are serialized with `cookie` and appended without replacing existing
124
+ `Set-Cookie` headers. Disconnected responses unsubscribe without continuing the
125
+ middleware chain. When the last waiting response disconnects, the login is aborted;
126
+ a later request starts a fresh login. Vite shutdown also cancels pending authentication.
127
+
128
+ ## Migrating from the class-based API
129
+
130
+ Version `0.2.0` changes the public API under the project's pre-1.0 versioning policy:
131
+
132
+ - Replace `request: instance` with `request: (config) => instance.create(config)`,
133
+ or inline the request construction as shown above.
134
+ - Replace `createAuthRequest` with a `request(config)` callback. The callback runs
135
+ per login, rather than once during server configuration.
136
+ - Replace `PluginConfig.fromEnv(env)` with `loadPluginConfig(env)`.
137
+ - Replace `new FormDataAuthRequest(config, fields)` with
138
+ `createFormDataRequest(config, fields)`. Call the returned function with `config`.
139
+ - `AuthRequest` is now a function type; `AuthRequestFactory` is removed.
140
+ - `AuthCookieOptions` is exported for reusable cookie configuration.
117
141
 
118
142
  ## Environment variables
119
143
 
120
144
  Define these variables in your Vite environment files, such as `.env.local`, `.env.development`, or another file in `envDir` that Vite loads for the current mode.
121
145
 
122
- Required by the default `FormDataAuthRequest` strategy only:
146
+ Required by the default form-data request only:
123
147
 
124
148
  | Variable | Description |
125
149
  | -------------------------- | -------------------------------------------------------------------------------------------- |
@@ -137,17 +161,13 @@ Optional variables:
137
161
 
138
162
  ## Configuration parsing
139
163
 
140
- `PluginConfig.fromEnv(env)` converts the environment values returned by Vite's
141
- `loadEnv` into readonly configuration properties. It applies the shared defaults
142
- and parses the TLS flag. Use this named factory instead of constructing
143
- `PluginConfig` directly.
164
+ `loadPluginConfig(env)` converts the environment values returned by Vite's
165
+ `loadEnv` into a frozen configuration object. It applies shared defaults and
166
+ parses the TLS flag.
144
167
 
145
168
  Login URL, username, and password are optional in the shared configuration.
146
- `FormDataAuthRequest` validates them when the plugin creates the default
147
- strategy and whenever it builds a request. A custom strategy or factory is
148
- responsible for validating the fields it uses.
149
-
150
- The login variables deliberately avoid the `VITE_` prefix so they remain on the server with Vite's default configuration. Do not add `API_AUTO_LOGIN_` to Vite's `envPrefix`, as this would expose credentials to client code.
169
+ `createFormDataRequest` validates them when constructing the default callback.
170
+ Custom request callbacks validate their own requirements.
151
171
 
152
172
  ## Vue + MSW example
153
173
 
@@ -189,7 +209,7 @@ Local environment overrides (`.env.local` and `.env.*.local`) are ignored by Git
189
209
  Use `@/` for imports from `src/`, including imports in tests:
190
210
 
191
211
  ```ts
192
- import { PluginConfig } from '@/config/PluginConfig'
212
+ import { loadPluginConfig } from '@/config/loadPluginConfig'
193
213
  ```
194
214
 
195
215
  TypeScript and Vite/Vitest resolve this alias locally. The declaration build
@@ -230,10 +250,9 @@ Release setup:
230
250
  as the repository Actions secret `NPM_TOKEN`. Use a stage-only token; CI does
231
251
  not need to bypass 2FA. Keep local tokens in an ignored `.env` file.
232
252
  2. Run `npm ci`, `npm run check`, and `npm run example:build`.
233
- 3. The renamed package's initial release uses the tag
234
- `@corespec/vite-plugin-api-auto-login@0.1.0`, preserving the old package's
235
- `v0.1.0` tag. For later releases, use `npm run release` or an explicit
236
- bump command described in CONTRIBUTING.md.
253
+ 3. Use `npm run release` or an explicit bump command described in CONTRIBUTING.md.
254
+ Releases use version tags such as `v0.2.0`. Historical release tags were removed
255
+ during the repository history reset; published npm versions remain available.
237
256
  4. Push the release commit and tag with `git push --follow-tags`. The Publish
238
257
  workflow checks, builds, and stages the package with provenance. The npm
239
258
  token is available only to its staging step.
@@ -242,7 +261,7 @@ Release setup:
242
261
  A successful staging workflow does not mean the version is publicly available.
243
262
 
244
263
  To retry an existing tag, manually run the Publish workflow and supply that tag
245
- (for example `v0.1.0`). It checks out the tagged source and verifies the version.
264
+ (for example `v0.2.0`). It checks out the tagged source and verifies the version.
246
265
 
247
266
  Trusted publishing can replace the token after configuring owner `core-spec-tech`,
248
267
  repository `vite-plugin-api-auto-login`, and workflow `publish.yml` in npm's package
@@ -4,7 +4,5 @@ export type AuthRequestOptions = {
4
4
  url: string | URL;
5
5
  init: Omit<RequestInit, 'dispatcher'>;
6
6
  };
7
- export interface AuthRequest {
8
- create(config: Readonly<PluginEnv>): AuthRequestOptions | Promise<AuthRequestOptions>;
9
- }
7
+ export type AuthRequest = (config: Readonly<PluginEnv>) => AuthRequestOptions | Promise<AuthRequestOptions>;
10
8
  //# sourceMappingURL=AuthRequest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuthRequest.d.ts","sourceRoot":"","sources":["../../src/auth/AuthRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AAEzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;IACjB,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;CACtC,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CACtF"}
1
+ {"version":3,"file":"AuthRequest.d.ts","sourceRoot":"","sources":["../../src/auth/AuthRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;IACjB,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,CACxB,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,KACxB,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { FormFieldNames, PluginEnv } from '../config/types.js';
2
+ import type { AuthRequest } from './AuthRequest.js';
3
+ export declare function createFormDataRequest(config: Readonly<PluginEnv>, fields?: Readonly<FormFieldNames>): AuthRequest;
4
+ //# sourceMappingURL=createFormDataRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createFormDataRequest.d.ts","sourceRoot":"","sources":["../../src/auth/createFormDataRequest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,EAC3B,MAAM,GAAE,QAAQ,CAAC,cAAc,CAAM,GACpC,WAAW,CAkBb"}
@@ -0,0 +1,14 @@
1
+ import type { Observable } from 'rxjs';
2
+ import { fetch as undiciFetch } from 'undici';
3
+ import type { Dispatcher } from 'undici';
4
+ import type { PluginEnv } from '../config/types.js';
5
+ import type { AuthRequest } from './AuthRequest.js';
6
+ type TokenStreamDependencies = {
7
+ request?: AuthRequest;
8
+ fetch?: typeof undiciFetch;
9
+ createDispatcher?: () => Dispatcher;
10
+ signal?: AbortSignal;
11
+ };
12
+ export declare function createTokenStream(config: Readonly<PluginEnv>, dependencies?: TokenStreamDependencies): Observable<string>;
13
+ export {};
14
+ //# sourceMappingURL=createTokenStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTokenStream.d.ts","sourceRoot":"","sources":["../../src/auth/createTokenStream.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AACtC,OAAO,EAAS,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAIrD,KAAK,uBAAuB,GAAG;IAC7B,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,WAAW,CAAA;IAC1B,gBAAgB,CAAC,EAAE,MAAM,UAAU,CAAA;IACnC,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,EAC3B,YAAY,GAAE,uBAA4B,GACzC,UAAU,CAAC,MAAM,CAAC,CAoDpB"}
@@ -0,0 +1,5 @@
1
+ import type { PluginEnv, PluginEnvInput } from './types.js';
2
+ export declare const DEFAULT_COOKIE_NAME = "your_token_name";
3
+ export declare const DEFAULT_USER_AGENT = "vite";
4
+ export declare function loadPluginConfig(env: PluginEnvInput): Readonly<PluginEnv>;
5
+ //# sourceMappingURL=loadPluginConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadPluginConfig.d.ts","sourceRoot":"","sources":["../../src/config/loadPluginConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/D,eAAO,MAAM,mBAAmB,oBAAoB,CAAA;AACpD,eAAO,MAAM,kBAAkB,SAAS,CAAA;AAExC,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,QAAQ,CAAC,SAAS,CAAC,CASzE"}
@@ -8,22 +8,23 @@ export type PluginEnv = {
8
8
  userAgent: string;
9
9
  };
10
10
  export type PluginEnvInput = Readonly<Record<string, string | undefined>>;
11
- export type AuthRequestFactory = (config: Readonly<PluginEnv>) => AuthRequest;
12
11
  export type FormFieldNames = {
13
12
  username?: string;
14
13
  password?: string;
15
14
  };
15
+ export type AuthCookieOptions = {
16
+ path?: string;
17
+ sameSite?: 'lax' | 'strict' | 'none';
18
+ secure?: boolean;
19
+ httpOnly?: boolean;
20
+ };
16
21
  export type AutoLoginOptions = {
22
+ cookie?: AuthCookieOptions;
23
+ } & ({
17
24
  formFields?: FormFieldNames;
18
25
  request?: never;
19
- createAuthRequest?: never;
20
26
  } | {
21
27
  formFields?: never;
22
28
  request: AuthRequest;
23
- createAuthRequest?: never;
24
- } | {
25
- formFields?: never;
26
- request?: never;
27
- createAuthRequest: AuthRequestFactory;
28
- };
29
+ });
29
30
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,MAAM,MAAM,SAAS,GAAG;IACtB,gBAAgB,EAAE,OAAO,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;AAEzE,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,WAAW,CAAA;AAE7E,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,UAAU,CAAC,EAAE,cAAc,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,iBAAiB,CAAC,EAAE,KAAK,CAAA;CAAE,GAC3E;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,WAAW,CAAC;IAAC,iBAAiB,CAAC,EAAE,KAAK,CAAA;CAAE,GACvE;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,iBAAiB,EAAE,kBAAkB,CAAA;CAAE,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,MAAM,MAAM,SAAS,GAAG;IACtB,gBAAgB,EAAE,OAAO,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;AAEzE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;IACpC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,GAAG,CAC9D;IAAE,UAAU,CAAC,EAAE,cAAc,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,CAChG,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ export declare function isHtmlDocumentRequest(req: IncomingMessage): boolean;
3
+ //# sourceMappingURL=isHtmlDocumentRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isHtmlDocumentRequest.d.ts","sourceRoot":"","sources":["../../src/http/isHtmlDocumentRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAKnE"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { viteAPIAutoLogin } from './plugin.js';
2
- export { FormDataAuthRequest } from './auth/FormDataAuthRequest.js';
2
+ export { createFormDataRequest } from './auth/createFormDataRequest.js';
3
+ export { loadPluginConfig } from './config/loadPluginConfig.js';
3
4
  export type { AuthRequest, AuthRequestOptions } from './auth/AuthRequest.js';
4
- export type { AutoLoginOptions, PluginEnv, AuthRequestFactory, FormFieldNames, } from './config/types.js';
5
- export { PluginConfig } from './config/PluginConfig.js';
5
+ export type { AutoLoginOptions, PluginEnv, FormFieldNames, AuthCookieOptions } from './config/types.js';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACzE,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,cAAc,GACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC5D,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACzE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,5 @@
1
+ import type { Observable } from 'rxjs';
2
+ import type { Connect, Logger } from 'vite';
3
+ import type { AuthCookieOptions, PluginEnv } from '../config/types.js';
4
+ export declare function createAutoLoginMiddleware(config: Readonly<PluginEnv>, cookie: Required<AuthCookieOptions>, token$: Observable<string>, logger: Pick<Logger, 'error'>, signal: AbortSignal): Connect.NextHandleFunction;
5
+ //# sourceMappingURL=createAutoLoginMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAutoLoginMiddleware.d.ts","sourceRoot":"","sources":["../../src/middleware/createAutoLoginMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAIlE,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,EAC3B,MAAM,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACnC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,EAC1B,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,kBAAkB,CA0C5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAMlC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGtD,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAoCvE"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAOlC,OAAO,KAAK,EAAqB,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGzE,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAoDvE"}
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("vite"),t=require("undici"),n=require("jose");var r=class{fields;constructor(e,t={}){if(this.fields={username:t.username??`username`,password:t.password??`password`},!this.fields.username.trim()||!this.fields.password.trim()||this.fields.username===this.fields.password)throw Error(`Form field names must be non-empty and distinct`);e&&this.validate(e)}create(e){let{username:n,password:r,loginUrl:i}=this.validate(e),a=new t.FormData;return a.set(this.fields.username,n),a.set(this.fields.password,r),{url:i,init:{method:`POST`,headers:{"User-Agent":e.userAgent},body:a}}}validate(e){let{loginUrl:t,username:n,password:r}=e;if(!t||!n||!r)throw Error(`Missing required env vars: API_AUTO_LOGIN_LOGIN_URL, API_AUTO_LOGIN_USERNAME, API_AUTO_LOGIN_PASSWORD`);return{loginUrl:t,username:n,password:r}}},i=`vite-plugin-api-auto-login`,a=class e{allowInsecureTls;cookieName;username;loginUrl;password;userAgent;static fromEnv(t){return new e(t)}constructor(e){this.allowInsecureTls=e.API_AUTO_LOGIN_ALLOW_INSECURE_TLS===`true`,this.cookieName=e.API_AUTO_LOGIN_COOKIE_NAME??`your_token_name`,this.username=e.API_AUTO_LOGIN_USERNAME,this.loginUrl=e.API_AUTO_LOGIN_LOGIN_URL,this.password=e.API_AUTO_LOGIN_PASSWORD,this.userAgent=e.API_AUTO_LOGIN_USER_AGENT??`vite`}},o=class{env;request;fetchImpl;dispatcher;cachedToken;pendingLogin;constructor(e,n={}){this.env=e,this.request=n.request??new r,this.fetchImpl=n.fetch??t.fetch,e.allowInsecureTls&&(this.dispatcher=n.createDispatcher?.()??new t.Agent({connect:{rejectUnauthorized:!1}}))}async getToken(){return this.cachedToken?.expiresAt!==void 0&&this.cachedToken.expiresAt>Date.now()?this.cachedToken.value:(this.pendingLogin??=this.requestToken().then(e=>(this.cachedToken=e,e.value)).finally(()=>{this.pendingLogin=void 0}),this.pendingLogin)}async requestToken(){let{url:e,init:t}=await this.request.create(this.env),n=await this.fetchImpl(e,{...t,dispatcher:this.dispatcher});if(!n.ok)throw Error(`Login request failed with status ${n.status}`);let r=await n.json();if(typeof r.token!=`string`||r.token.length===0)throw Error(`Login response did not include a token`);return{expiresAt:this.parseJwtExpiry(r.token),value:r.token}}parseJwtExpiry(e){try{let{exp:t}=(0,n.decodeJwt)(e);return typeof t==`number`?t*1e3:void 0}catch{return}}},s=class{response;constructor(e){this.response=e}setAuthCookie(e,t){let n=`${e}=${t}; Path=/; SameSite=Lax`,r=this.response.getHeader(`Set-Cookie`);if(!r){this.response.setHeader(`Set-Cookie`,n);return}let i;i=Array.isArray(r)?r:[String(r)],this.response.setHeader(`Set-Cookie`,[...i,n])}},c=`text/html`,l=class{static matches(e){return e.headers.upgrade?.toLowerCase()===`websocket`||e.method!==`GET`&&e.method!==`HEAD`?!1:e.headers.accept?.includes(c)??!1}},u=class{config;logger;request;session;constructor(e,t,n){this.config=e,this.logger=t,this.request=n}handle=async(e,t,n)=>{if(!l.matches(e))n();else{try{this.session??=new o(this.config,{request:this.request});let e=await this.session.getToken();new s(t).setAuthCookie(this.config.cookieName,e)}catch(e){let t=e instanceof Error?e.message:String(e),n=this.config.loginUrl?` (${this.config.loginUrl})`:``;this.logger.error(`[${i}] ${t}${n}`)}n()}}};function d(t={}){if(t.request!==void 0&&t.createAuthRequest!==void 0)throw Error(`Use either request or createAuthRequest, not both`);if(t.formFields!==void 0&&(t.request!==void 0||t.createAuthRequest!==void 0))throw Error(`Use formFields only with the default request strategy`);return{name:i,apply:`serve`,configureServer(n){let{mode:o,envDir:s,logger:c}=n.config;try{let i=a.fromEnv((0,e.loadEnv)(o,s,`API_AUTO_LOGIN_`)),l=t.request;if(t.createAuthRequest?l=t.createAuthRequest(i):l||=new r(i,t.formFields),!l||typeof l.create!=`function`)throw Error(`Token request must provide a create method`);let d=new u(i,c,l);n.middlewares.use(d.handle)}catch(e){c.error(`[${i}] ${e instanceof Error?e.message:String(e)}`)}}}}exports.FormDataAuthRequest=r,exports.PluginConfig=a,exports.viteAPIAutoLogin=d;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("cookie"),t=require("vite"),n=require("undici"),r=require("jose"),i=require("rxjs");function a(e,t={}){let r=t.username??`username`,i=t.password??`password`;if(!r.trim()||!i.trim()||r===i)throw Error(`Form field names must be non-empty and distinct`);return o(e),e=>{let{loginUrl:t,username:a,password:s}=o(e),c=new n.FormData;return c.set(r,a),c.set(i,s),{url:t,init:{method:`POST`,headers:{"User-Agent":e.userAgent},body:c}}}}function o(e){let{loginUrl:t,username:n,password:r}=e;if(!t||!n||!r)throw Error(`Missing required env vars: API_AUTO_LOGIN_LOGIN_URL, API_AUTO_LOGIN_USERNAME, API_AUTO_LOGIN_PASSWORD`);return{loginUrl:t,username:n,password:r}}function s(e,t={}){let r=t.request??a(e),o=t.fetch??n.fetch,s;return(0,i.defer)(()=>{if(t.signal?.aborted)return i.EMPTY;if(s?.expiresAt!==void 0&&s.expiresAt>Date.now())return(0,i.of)(s.value);let a=new AbortController,l;return e.allowInsecureTls&&(l=t.createDispatcher?.()??new n.Agent({connect:{rejectUnauthorized:!1}})),(0,i.defer)(()=>(0,i.from)(Promise.resolve(r(e)))).pipe((0,i.switchMap)(({url:e,init:n})=>{let r=[a.signal];return n.signal&&r.push(n.signal),t.signal&&r.push(t.signal),(0,i.from)(o(e,{...n,dispatcher:l,signal:AbortSignal.any(r)}))}),(0,i.switchMap)(e=>{if(!e.ok)throw Error(`Login request failed with status ${e.status}`);return(0,i.from)(e.json())}),(0,i.map)(c),(0,i.tap)(e=>{s=e}),(0,i.map)(e=>e.value),(0,i.takeUntil)(t.signal?(0,i.fromEvent)(t.signal,`abort`):i.EMPTY),(0,i.finalize)(()=>{a.abort(),l&&l.destroy().catch(()=>{})}))}).pipe((0,i.share)({connector:()=>new i.ReplaySubject(1),resetOnError:!0,resetOnComplete:!0,resetOnRefCountZero:!0}))}function c(e){if(!e||typeof e!=`object`||!(`token`in e)||typeof e.token!=`string`||e.token.length===0)throw Error(`Login response did not include a token`);let t=e.token;try{let{exp:e}=(0,r.decodeJwt)(t);return{value:t,expiresAt:typeof e==`number`?e*1e3:void 0}}catch{return{value:t}}}var l=`vite-plugin-api-auto-login`;function u(e){return Object.freeze({allowInsecureTls:e.API_AUTO_LOGIN_ALLOW_INSECURE_TLS===`true`,cookieName:e.API_AUTO_LOGIN_COOKIE_NAME??`your_token_name`,username:e.API_AUTO_LOGIN_USERNAME,loginUrl:e.API_AUTO_LOGIN_LOGIN_URL,password:e.API_AUTO_LOGIN_PASSWORD,userAgent:e.API_AUTO_LOGIN_USER_AGENT??`vite`})}function d(e){let t=e.method===`GET`||e.method===`HEAD`,n=e.headers.accept?.includes(`text/html`)??!1,r=e.headers.upgrade?.toLowerCase()===`websocket`;return t&&n&&!r}function f(t,n,r,a,o){return(s,c,u)=>{if(o.aborted||c.destroyed||c.writableEnded)return;let f=new i.Subscription,p=()=>f.unsubscribe(),m=()=>{o.aborted||c.destroyed||c.writableEnded||f.closed||u()};c.once(`close`,p),o.addEventListener(`abort`,p,{once:!0}),f.add(()=>{c.off(`close`,p),o.removeEventListener(`abort`,p)}),f.add((0,i.of)(s).pipe((0,i.filter)(d),(0,i.switchMap)(()=>r),(0,i.take)(1),(0,i.map)(r=>(0,e.stringifySetCookie)({...n,name:t.cookieName,value:r})),(0,i.tap)(e=>{c.headersSent||c.destroyed||c.writableEnded||c.appendHeader(`Set-Cookie`,e)})).subscribe({error:e=>{let n=e instanceof Error?e.message:String(e),r=t.loginUrl?` (${t.loginUrl})`:``;a.error(`[${l}] ${n}${r}`),m(),f.unsubscribe()},complete:()=>{m(),f.unsubscribe()}}))}}function p(n={}){if(`createAuthRequest`in n)throw Error(`createAuthRequest was removed; use request(config) instead`);if(n.request!==void 0&&typeof n.request!=`function`)throw Error(`request must be a function: request(config)`);if(n.formFields!==void 0&&n.request!==void 0)throw Error(`Use formFields only with the default request`);let r={path:n.cookie?.path??`/`,sameSite:n.cookie?.sameSite??`lax`,secure:n.cookie?.secure??!1,httpOnly:n.cookie?.httpOnly??!1};if(r.sameSite===`none`&&!r.secure)throw Error(`cookie.sameSite "none" requires cookie.secure to be true`);(0,e.stringifySetCookie)({...r,name:`validation`,value:``});let i=new Set;return{name:l,apply:`serve`,configureServer(o){let{mode:c,envDir:d,logger:p}=o.config;try{let l=u((0,t.loadEnv)(c,d,`API_AUTO_LOGIN_`));(0,e.stringifySetCookie)({...r,name:l.cookieName,value:``});let m=n.request??a(l,n.formFields),h=new AbortController,g=()=>{h.abort(),o.httpServer?.off(`close`,g),i.delete(g)};i.add(g),o.httpServer?.once(`close`,g);let _=s(l,{request:m,signal:h.signal});o.middlewares.use(f(l,r,_,p,h.signal))}catch(e){p.error(`[${l}] ${e instanceof Error?e.message:String(e)}`)}},closeBundle(){for(let e of i)e()}}}exports.createFormDataRequest=a,exports.loadPluginConfig=u,exports.viteAPIAutoLogin=p;
@@ -1,144 +1,164 @@
1
- import { loadEnv as e } from "vite";
2
- import { Agent as t, FormData as n, fetch as r } from "undici";
3
- import { decodeJwt as i } from "jose";
4
- // src/auth/FormDataAuthRequest.ts
5
- var a = class {
6
- fields;
7
- constructor(e, t = {}) {
8
- if (this.fields = {
9
- username: t.username ?? "username",
10
- password: t.password ?? "password"
11
- }, !this.fields.username.trim() || !this.fields.password.trim() || this.fields.username === this.fields.password) throw Error("Form field names must be non-empty and distinct");
12
- e && this.validate(e);
13
- }
14
- create(e) {
15
- let { username: t, password: r, loginUrl: i } = this.validate(e), a = new n();
16
- return a.set(this.fields.username, t), a.set(this.fields.password, r), {
17
- url: i,
1
+ import { stringifySetCookie as e } from "cookie";
2
+ import { loadEnv as t } from "vite";
3
+ import { Agent as n, FormData as r, fetch as i } from "undici";
4
+ import { decodeJwt as a } from "jose";
5
+ import { EMPTY as o, ReplaySubject as s, Subscription as c, defer as l, filter as u, finalize as d, from as f, fromEvent as p, map as m, of as h, share as g, switchMap as _, take as v, takeUntil as y, tap as b } from "rxjs";
6
+ // src/auth/createFormDataRequest.ts
7
+ function x(e, t = {}) {
8
+ let n = t.username ?? "username", i = t.password ?? "password";
9
+ if (!n.trim() || !i.trim() || n === i) throw Error("Form field names must be non-empty and distinct");
10
+ return S(e), (e) => {
11
+ let { loginUrl: t, username: a, password: o } = S(e), s = new r();
12
+ return s.set(n, a), s.set(i, o), {
13
+ url: t,
18
14
  init: {
19
15
  method: "POST",
20
16
  headers: { "User-Agent": e.userAgent },
21
- body: a
17
+ body: s
22
18
  }
23
19
  };
24
- }
25
- validate(e) {
26
- let { loginUrl: t, username: n, password: r } = e;
27
- if (!t || !n || !r) throw Error("Missing required env vars: API_AUTO_LOGIN_LOGIN_URL, API_AUTO_LOGIN_USERNAME, API_AUTO_LOGIN_PASSWORD");
20
+ };
21
+ }
22
+ function S(e) {
23
+ let { loginUrl: t, username: n, password: r } = e;
24
+ if (!t || !n || !r) throw Error("Missing required env vars: API_AUTO_LOGIN_LOGIN_URL, API_AUTO_LOGIN_USERNAME, API_AUTO_LOGIN_PASSWORD");
25
+ return {
26
+ loginUrl: t,
27
+ username: n,
28
+ password: r
29
+ };
30
+ }
31
+ // src/auth/createTokenStream.ts
32
+ function C(e, t = {}) {
33
+ let r = t.request ?? x(e), a = t.fetch ?? i, c;
34
+ return l(() => {
35
+ if (t.signal?.aborted) return o;
36
+ if (c?.expiresAt !== void 0 && c.expiresAt > Date.now()) return h(c.value);
37
+ let i = new AbortController(), s;
38
+ return e.allowInsecureTls && (s = t.createDispatcher?.() ?? new n({ connect: { rejectUnauthorized: !1 } })), l(() => f(Promise.resolve(r(e)))).pipe(_(({ url: e, init: n }) => {
39
+ let r = [i.signal];
40
+ return n.signal && r.push(n.signal), t.signal && r.push(t.signal), f(a(e, {
41
+ ...n,
42
+ dispatcher: s,
43
+ signal: AbortSignal.any(r)
44
+ }));
45
+ }), _((e) => {
46
+ if (!e.ok) throw Error(`Login request failed with status ${e.status}`);
47
+ return f(e.json());
48
+ }), m(w), b((e) => {
49
+ c = e;
50
+ }), m((e) => e.value), y(t.signal ? p(t.signal, "abort") : o), d(() => {
51
+ i.abort(), s && s.destroy().catch(() => {});
52
+ }));
53
+ }).pipe(g({
54
+ connector: () => new s(1),
55
+ resetOnError: !0,
56
+ resetOnComplete: !0,
57
+ resetOnRefCountZero: !0
58
+ }));
59
+ }
60
+ function w(e) {
61
+ if (!e || typeof e != "object" || !("token" in e) || typeof e.token != "string" || e.token.length === 0) throw Error("Login response did not include a token");
62
+ let t = e.token;
63
+ try {
64
+ let { exp: e } = a(t);
28
65
  return {
29
- loginUrl: t,
30
- username: n,
31
- password: r
66
+ value: t,
67
+ expiresAt: typeof e == "number" ? e * 1e3 : void 0
32
68
  };
69
+ } catch {
70
+ return { value: t };
33
71
  }
34
- }, o = "vite-plugin-api-auto-login", s = class e {
35
- allowInsecureTls;
36
- cookieName;
37
- username;
38
- loginUrl;
39
- password;
40
- userAgent;
41
- static fromEnv(t) {
42
- return new e(t);
43
- }
44
- constructor(e) {
45
- this.allowInsecureTls = e.API_AUTO_LOGIN_ALLOW_INSECURE_TLS === "true", this.cookieName = e.API_AUTO_LOGIN_COOKIE_NAME ?? "your_token_name", this.username = e.API_AUTO_LOGIN_USERNAME, this.loginUrl = e.API_AUTO_LOGIN_LOGIN_URL, this.password = e.API_AUTO_LOGIN_PASSWORD, this.userAgent = e.API_AUTO_LOGIN_USER_AGENT ?? "vite";
46
- }
47
- }, c = class {
48
- env;
49
- request;
50
- fetchImpl;
51
- dispatcher;
52
- cachedToken;
53
- pendingLogin;
54
- constructor(e, n = {}) {
55
- this.env = e, this.request = n.request ?? new a(), this.fetchImpl = n.fetch ?? r, e.allowInsecureTls && (this.dispatcher = n.createDispatcher?.() ?? new t({ connect: { rejectUnauthorized: !1 } }));
56
- }
57
- async getToken() {
58
- return this.cachedToken?.expiresAt !== void 0 && this.cachedToken.expiresAt > Date.now() ? this.cachedToken.value : (this.pendingLogin ??= this.requestToken().then((e) => (this.cachedToken = e, e.value)).finally(() => {
59
- this.pendingLogin = void 0;
60
- }), this.pendingLogin);
61
- }
62
- async requestToken() {
63
- let { url: e, init: t } = await this.request.create(this.env), n = await this.fetchImpl(e, {
64
- ...t,
65
- dispatcher: this.dispatcher
66
- });
67
- if (!n.ok) throw Error(`Login request failed with status ${n.status}`);
68
- let r = await n.json();
69
- if (typeof r.token != "string" || r.token.length === 0) throw Error("Login response did not include a token");
70
- return {
71
- expiresAt: this.parseJwtExpiry(r.token),
72
- value: r.token
72
+ }
73
+ // src/constants.ts
74
+ var T = "vite-plugin-api-auto-login";
75
+ function E(e) {
76
+ return Object.freeze({
77
+ allowInsecureTls: e.API_AUTO_LOGIN_ALLOW_INSECURE_TLS === "true",
78
+ cookieName: e.API_AUTO_LOGIN_COOKIE_NAME ?? "your_token_name",
79
+ username: e.API_AUTO_LOGIN_USERNAME,
80
+ loginUrl: e.API_AUTO_LOGIN_LOGIN_URL,
81
+ password: e.API_AUTO_LOGIN_PASSWORD,
82
+ userAgent: e.API_AUTO_LOGIN_USER_AGENT ?? "vite"
83
+ });
84
+ }
85
+ // src/http/isHtmlDocumentRequest.ts
86
+ function D(e) {
87
+ let t = e.method === "GET" || e.method === "HEAD", n = e.headers.accept?.includes("text/html") ?? !1, r = e.headers.upgrade?.toLowerCase() === "websocket";
88
+ return t && n && !r;
89
+ }
90
+ // src/middleware/createAutoLoginMiddleware.ts
91
+ function O(t, n, r, i, a) {
92
+ return (o, s, l) => {
93
+ if (a.aborted || s.destroyed || s.writableEnded) return;
94
+ let d = new c(), f = () => d.unsubscribe(), p = () => {
95
+ a.aborted || s.destroyed || s.writableEnded || d.closed || l();
73
96
  };
74
- }
75
- parseJwtExpiry(e) {
76
- try {
77
- let { exp: t } = i(e);
78
- return typeof t == "number" ? t * 1e3 : void 0;
79
- } catch {
80
- return;
81
- }
82
- }
83
- }, l = class {
84
- response;
85
- constructor(e) {
86
- this.response = e;
87
- }
88
- setAuthCookie(e, t) {
89
- let n = `${e}=${t}; Path=/; SameSite=Lax`, r = this.response.getHeader("Set-Cookie");
90
- if (!r) {
91
- this.response.setHeader("Set-Cookie", n);
92
- return;
93
- }
94
- let i;
95
- i = Array.isArray(r) ? r : [String(r)], this.response.setHeader("Set-Cookie", [...i, n]);
96
- }
97
- }, u = "text/html", d = class {
98
- static matches(e) {
99
- return e.headers.upgrade?.toLowerCase() === "websocket" || e.method !== "GET" && e.method !== "HEAD" ? !1 : e.headers.accept?.includes(u) ?? !1;
100
- }
101
- }, f = class {
102
- config;
103
- logger;
104
- request;
105
- session;
106
- constructor(e, t, n) {
107
- this.config = e, this.logger = t, this.request = n;
108
- }
109
- handle = async (e, t, n) => {
110
- if (!d.matches(e)) n();
111
- else {
112
- try {
113
- this.session ??= new c(this.config, { request: this.request });
114
- let e = await this.session.getToken();
115
- new l(t).setAuthCookie(this.config.cookieName, e);
116
- } catch (e) {
117
- let t = e instanceof Error ? e.message : String(e), n = this.config.loginUrl ? ` (${this.config.loginUrl})` : "";
118
- this.logger.error(`[${o}] ${t}${n}`);
97
+ s.once("close", f), a.addEventListener("abort", f, { once: !0 }), d.add(() => {
98
+ s.off("close", f), a.removeEventListener("abort", f);
99
+ }), d.add(h(o).pipe(u(D), _(() => r), v(1), m((r) => e({
100
+ ...n,
101
+ name: t.cookieName,
102
+ value: r
103
+ })), b((e) => {
104
+ s.headersSent || s.destroyed || s.writableEnded || s.appendHeader("Set-Cookie", e);
105
+ })).subscribe({
106
+ error: (e) => {
107
+ let n = e instanceof Error ? e.message : String(e), r = t.loginUrl ? ` (${t.loginUrl})` : "";
108
+ i.error(`[${T}] ${n}${r}`), p(), d.unsubscribe();
109
+ },
110
+ complete: () => {
111
+ p(), d.unsubscribe();
119
112
  }
120
- n();
121
- }
113
+ }));
122
114
  };
123
- };
115
+ }
124
116
  // src/plugin.ts
125
- function p(t = {}) {
126
- if (t.request !== void 0 && t.createAuthRequest !== void 0) throw Error("Use either request or createAuthRequest, not both");
127
- if (t.formFields !== void 0 && (t.request !== void 0 || t.createAuthRequest !== void 0)) throw Error("Use formFields only with the default request strategy");
117
+ function k(n = {}) {
118
+ if ("createAuthRequest" in n) throw Error("createAuthRequest was removed; use request(config) instead");
119
+ if (n.request !== void 0 && typeof n.request != "function") throw Error("request must be a function: request(config)");
120
+ if (n.formFields !== void 0 && n.request !== void 0) throw Error("Use formFields only with the default request");
121
+ let r = {
122
+ path: n.cookie?.path ?? "/",
123
+ sameSite: n.cookie?.sameSite ?? "lax",
124
+ secure: n.cookie?.secure ?? !1,
125
+ httpOnly: n.cookie?.httpOnly ?? !1
126
+ };
127
+ if (r.sameSite === "none" && !r.secure) throw Error("cookie.sameSite \"none\" requires cookie.secure to be true");
128
+ e({
129
+ ...r,
130
+ name: "validation",
131
+ value: ""
132
+ });
133
+ let i = /* @__PURE__ */ new Set();
128
134
  return {
129
- name: o,
135
+ name: T,
130
136
  apply: "serve",
131
- configureServer(n) {
132
- let { mode: r, envDir: i, logger: c } = n.config;
137
+ configureServer(a) {
138
+ let { mode: o, envDir: s, logger: c } = a.config;
133
139
  try {
134
- let o = s.fromEnv(e(r, i, "API_AUTO_LOGIN_")), l = t.request;
135
- if (t.createAuthRequest ? l = t.createAuthRequest(o) : l ||= new a(o, t.formFields), !l || typeof l.create != "function") throw Error("Token request must provide a create method");
136
- let u = new f(o, c, l);
137
- n.middlewares.use(u.handle);
140
+ let l = E(t(o, s, "API_AUTO_LOGIN_"));
141
+ e({
142
+ ...r,
143
+ name: l.cookieName,
144
+ value: ""
145
+ });
146
+ let u = n.request ?? x(l, n.formFields), d = new AbortController(), f = () => {
147
+ d.abort(), a.httpServer?.off("close", f), i.delete(f);
148
+ };
149
+ i.add(f), a.httpServer?.once("close", f);
150
+ let p = C(l, {
151
+ request: u,
152
+ signal: d.signal
153
+ });
154
+ a.middlewares.use(O(l, r, p, c, d.signal));
138
155
  } catch (e) {
139
- c.error(`[${o}] ${e instanceof Error ? e.message : String(e)}`);
156
+ c.error(`[${T}] ${e instanceof Error ? e.message : String(e)}`);
140
157
  }
158
+ },
159
+ closeBundle() {
160
+ for (let e of i) e();
141
161
  }
142
162
  };
143
163
  }
144
- export { a as FormDataAuthRequest, s as PluginConfig, p as viteAPIAutoLogin };
164
+ export { x as createFormDataRequest, E as loadPluginConfig, k as viteAPIAutoLogin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corespec/vite-plugin-api-auto-login",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Reusable Vite plugin for API auto login during local development",
5
5
  "type": "module",
6
6
  "main": "./dist/vite-plugin-api-auto-login.cjs",
@@ -68,7 +68,9 @@
68
68
  "vue-tsc": "^3.3.14"
69
69
  },
70
70
  "dependencies": {
71
+ "cookie": "^2.0.2",
71
72
  "jose": "^6.2.15",
73
+ "rxjs": "^7.8.2",
72
74
  "undici": "^8.12.3"
73
75
  },
74
76
  "peerDependencies": {
@@ -1,24 +0,0 @@
1
- import { fetch as undiciFetch } from 'undici';
2
- import type { Dispatcher } from 'undici';
3
- import type { PluginEnv } from '../config/types.js';
4
- import type { AuthRequest } from './AuthRequest.js';
5
- type FetchLike = typeof undiciFetch;
6
- type AuthSessionDependencies = {
7
- request?: AuthRequest;
8
- createDispatcher?: () => Dispatcher;
9
- fetch?: FetchLike;
10
- };
11
- export declare class AuthSession {
12
- private readonly env;
13
- private readonly request;
14
- private readonly fetchImpl;
15
- private readonly dispatcher;
16
- private cachedToken;
17
- private pendingLogin;
18
- constructor(env: PluginEnv, dependencies?: AuthSessionDependencies);
19
- getToken(): Promise<string>;
20
- private requestToken;
21
- private parseJwtExpiry;
22
- }
23
- export {};
24
- //# sourceMappingURL=AuthSession.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AuthSession.d.ts","sourceRoot":"","sources":["../../src/auth/AuthSession.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAOrD,KAAK,SAAS,GAAG,OAAO,WAAW,CAAA;AAEnC,KAAK,uBAAuB,GAAG;IAC7B,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,UAAU,CAAA;IACnC,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB,CAAA;AAED,qBAAa,WAAW;IAQpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IACnD,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,YAAY,CAA6B;gBAG9B,GAAG,EAAE,SAAS,EAC/B,YAAY,GAAE,uBAA4B;IAUtC,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;YAiBnB,YAAY;IAuB1B,OAAO,CAAC,cAAc;CASvB"}
@@ -1,9 +0,0 @@
1
- import type { FormFieldNames, PluginEnv } from '../config/types.js';
2
- import type { AuthRequest, AuthRequestOptions } from './AuthRequest.js';
3
- export declare class FormDataAuthRequest implements AuthRequest {
4
- private readonly fields;
5
- constructor(config?: Readonly<PluginEnv>, fields?: Readonly<FormFieldNames>);
6
- create(config: Readonly<PluginEnv>): AuthRequestOptions;
7
- private validate;
8
- }
9
- //# sourceMappingURL=FormDataAuthRequest.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FormDataAuthRequest.d.ts","sourceRoot":"","sources":["../../src/auth/FormDataAuthRequest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAEzE,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;gBAErC,MAAM,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAE,QAAQ,CAAC,cAAc,CAAM;IAkB/E,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,kBAAkB;IAgBvD,OAAO,CAAC,QAAQ;CASjB"}
@@ -1,14 +0,0 @@
1
- import type { PluginEnv, PluginEnvInput } from './types.js';
2
- export declare const DEFAULT_COOKIE_NAME = "your_token_name";
3
- export declare const DEFAULT_USER_AGENT = "vite";
4
- export declare class PluginConfig implements PluginEnv {
5
- readonly allowInsecureTls: boolean;
6
- readonly cookieName: string;
7
- readonly username: string | undefined;
8
- readonly loginUrl: string | undefined;
9
- readonly password: string | undefined;
10
- readonly userAgent: string;
11
- static fromEnv(env: PluginEnvInput): PluginConfig;
12
- private constructor();
13
- }
14
- //# sourceMappingURL=PluginConfig.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PluginConfig.d.ts","sourceRoot":"","sources":["../../src/config/PluginConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/D,eAAO,MAAM,mBAAmB,oBAAoB,CAAA;AACpD,eAAO,MAAM,kBAAkB,SAAS,CAAA;AAExC,qBAAa,YAAa,YAAW,SAAS;IAC5C,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAE1B,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY;IAIjD,OAAO;CAQR"}
@@ -1,7 +0,0 @@
1
- import type { ServerResponse } from 'node:http';
2
- export declare class AuthResponse {
3
- private readonly response;
4
- constructor(response: ServerResponse);
5
- setAuthCookie(cookieName: string, token: string): void;
6
- }
7
- //# sourceMappingURL=AuthResponse.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AuthResponse.d.ts","sourceRoot":"","sources":["../../src/http/AuthResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE/C,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,cAAc;IAErD,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;CAkBvD"}
@@ -1,5 +0,0 @@
1
- import type { IncomingMessage } from 'node:http';
2
- export declare class HtmlDocumentRequest {
3
- static matches(req: IncomingMessage): boolean;
4
- }
5
- //# sourceMappingURL=HtmlDocumentRequest.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HtmlDocumentRequest.d.ts","sourceRoot":"","sources":["../../src/http/HtmlDocumentRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAIhD,qBAAa,mBAAmB;IAC9B,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO;CAW9C"}
@@ -1,12 +0,0 @@
1
- import type { Connect, Logger } from 'vite';
2
- import type { AuthRequest } from '../auth/AuthRequest.js';
3
- import type { PluginEnv } from '../config/types.js';
4
- export declare class AutoLoginMiddleware {
5
- private readonly config;
6
- private readonly logger;
7
- private readonly request;
8
- private session;
9
- constructor(config: PluginEnv, logger: Pick<Logger, 'error'>, request: AuthRequest);
10
- readonly handle: Connect.NextHandleFunction;
11
- }
12
- //# sourceMappingURL=AutoLoginMiddleware.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AutoLoginMiddleware.d.ts","sourceRoot":"","sources":["../../src/middleware/AutoLoginMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAG3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAK/C,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,OAAO,CAAyB;gBAGrB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,WAAW;IAGvC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAiB1C;CACF"}