@abapify/adt-playwright 0.4.1 → 0.4.4
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/dist/index.d.mts +0 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +43 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BrowserAuthOptions, BrowserAuthOptions as BrowserAuthOptions$1, BrowserCredentials, BrowserCredentials as BrowserCredentials$1, CookieData, testCredentials, toCookieHeader, toHeaders } from "@abapify/browser-auth";
|
|
2
2
|
import { AdtConfig } from "@abapify/adt-config";
|
|
3
3
|
import { AuthPlugin } from "@abapify/adt-auth";
|
|
4
|
-
|
|
5
4
|
//#region src/playwright-auth.d.ts
|
|
6
5
|
type PlaywrightCredentials = BrowserCredentials$1;
|
|
7
6
|
type PlaywrightAuthOptions = BrowserAuthOptions$1;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/playwright-auth.ts","../src/auth-plugin.ts","../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/playwright-auth.ts","../src/auth-plugin.ts","../src/index.ts"],"mappings":";;;;KAeY,wBAAwB;KACxB,wBAAwB;;;;cAKvB;;;;EAKA,aAAA,SAAA,wBACR,QAAQ;;;;;;;;EAaY,QAAA;IACrB;MAAS;QAAkB,OAAO;;;MAChC,QAAQ;;cAaD;;;;EA7BA,aAAA,SAAA,wBACR,QAAQ;;;;;;;;EAaY,QAAA;IACrB;MAAS;QAAkB,OAAO;;;MAChC,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cCkCR,YAAY;;;;;;KCpDN,0BAA0B;;;;iBAetB,eACd,QAAQ,WACR,UAAU,QAAQ,wBACjB"}
|
package/dist/index.mjs
CHANGED
|
@@ -114,10 +114,20 @@ function createPlaywrightAdapter() {
|
|
|
114
114
|
* Playwright authentication object
|
|
115
115
|
*/
|
|
116
116
|
const playwrightAuth = {
|
|
117
|
+
/**
|
|
118
|
+
* Authenticate using Playwright browser
|
|
119
|
+
*/
|
|
117
120
|
async authenticate(options) {
|
|
118
|
-
|
|
121
|
+
const adapter = createPlaywrightAdapter();
|
|
122
|
+
return authenticate(adapter, options);
|
|
119
123
|
},
|
|
124
|
+
/**
|
|
125
|
+
* Test if credentials are still valid
|
|
126
|
+
*/
|
|
120
127
|
test: testCredentials,
|
|
128
|
+
/**
|
|
129
|
+
* Refresh is not reliable with Okta SSO - always return null to trigger full re-authentication
|
|
130
|
+
*/
|
|
121
131
|
async refresh(session) {
|
|
122
132
|
(session?.auth?.pluginOptions?.log ?? console.log)("🔄 Session expired, will trigger full re-authentication...");
|
|
123
133
|
return null;
|
|
@@ -128,7 +138,7 @@ const playwright = playwrightAuth;
|
|
|
128
138
|
//#endregion
|
|
129
139
|
//#region src/auth-plugin.ts
|
|
130
140
|
/** Default session duration when cookies don't specify expiration (8 hours) */
|
|
131
|
-
const DEFAULT_SESSION_DURATION_MS =
|
|
141
|
+
const DEFAULT_SESSION_DURATION_MS = 288e5;
|
|
132
142
|
/**
|
|
133
143
|
* Determines the session expiration time from cookie data.
|
|
134
144
|
*
|
|
@@ -178,6 +188,16 @@ function getExpiresAt(cookies) {
|
|
|
178
188
|
* ```
|
|
179
189
|
*/
|
|
180
190
|
const authPlugin = {
|
|
191
|
+
/**
|
|
192
|
+
* Authenticate using Playwright browser automation.
|
|
193
|
+
*
|
|
194
|
+
* Opens a browser window, navigates to the SAP system, and waits for
|
|
195
|
+
* the user to complete SSO authentication. Captures cookies and returns
|
|
196
|
+
* them in AuthPluginResult format.
|
|
197
|
+
*
|
|
198
|
+
* @param options - Authentication options including URL and browser settings
|
|
199
|
+
* @returns Promise resolving to credentials in AuthPluginResult format
|
|
200
|
+
*/
|
|
181
201
|
async authenticate(options) {
|
|
182
202
|
const credentials = await playwrightAuth.authenticate(options);
|
|
183
203
|
return {
|
|
@@ -188,6 +208,17 @@ const authPlugin = {
|
|
|
188
208
|
}
|
|
189
209
|
};
|
|
190
210
|
},
|
|
211
|
+
/**
|
|
212
|
+
* Attempt to refresh an existing session.
|
|
213
|
+
*
|
|
214
|
+
* @remarks
|
|
215
|
+
* Always returns null because Okta SSO sessions cannot be silently refreshed.
|
|
216
|
+
* When a session expires, full re-authentication with user interaction is required.
|
|
217
|
+
* The AuthManager will handle this by calling authenticate() again.
|
|
218
|
+
*
|
|
219
|
+
* @param _session - The existing session (unused)
|
|
220
|
+
* @returns Always null, indicating refresh is not supported
|
|
221
|
+
*/
|
|
191
222
|
async refresh(_session) {
|
|
192
223
|
return null;
|
|
193
224
|
}
|
|
@@ -210,15 +241,16 @@ function createPlaywrightDestination(options) {
|
|
|
210
241
|
function withPlaywright(config, options) {
|
|
211
242
|
if (!config.destinations) return config;
|
|
212
243
|
const destinations = {};
|
|
213
|
-
for (const [name, dest] of Object.entries(config.destinations)) if (typeof dest === "object" && "type" in dest)
|
|
214
|
-
type
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
244
|
+
for (const [name, dest] of Object.entries(config.destinations)) if (typeof dest === "object" && "type" in dest) {
|
|
245
|
+
if (dest.type === "@abapify/adt-playwright" || dest.type === "playwright") destinations[name] = {
|
|
246
|
+
type: "@abapify/adt-playwright",
|
|
247
|
+
options: {
|
|
248
|
+
...options,
|
|
249
|
+
...dest.options
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
else destinations[name] = dest;
|
|
253
|
+
} else if (typeof dest === "string") destinations[name] = createPlaywrightDestination({
|
|
222
254
|
url: dest,
|
|
223
255
|
...options
|
|
224
256
|
});
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter.ts","../src/playwright-auth.ts","../src/auth-plugin.ts","../src/index.ts"],"sourcesContent":["/**\n * Playwright Browser Adapter\n *\n * Implements BrowserAdapter interface for Playwright.\n * This is a thin wrapper - all auth logic is in @abapify/browser-auth.\n */\n\nimport { chromium } from 'playwright';\nimport type { BrowserContext, Page, Cookie } from 'playwright';\nimport type {\n BrowserAdapter,\n CookieData,\n ResponseEvent,\n} from '@abapify/browser-auth';\n\n/**\n * Convert Playwright cookie to CookieData\n */\nfunction convertCookie(cookie: Cookie): CookieData {\n return {\n name: cookie.name,\n value: cookie.value,\n domain: cookie.domain,\n path: cookie.path,\n expires: cookie.expires,\n httpOnly: cookie.httpOnly,\n secure: cookie.secure,\n sameSite: cookie.sameSite as CookieData['sameSite'],\n };\n}\n\n/**\n * Create a Playwright browser adapter\n */\nexport function createPlaywrightAdapter(): BrowserAdapter {\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n const responseHandlers: ((event: ResponseEvent) => void)[] = [];\n const closeHandlers: (() => void)[] = [];\n\n return {\n async launch(options) {\n const { headless, userDataDir, ignoreHTTPSErrors, userAgent } = options;\n\n if (userDataDir) {\n // Persistent context - stores cookies/session\n context = await chromium.launchPersistentContext(userDataDir, {\n headless,\n ignoreHTTPSErrors,\n userAgent,\n });\n } else {\n const browser = await chromium.launch({ headless });\n context = await browser.newContext({ ignoreHTTPSErrors, userAgent });\n }\n },\n\n async newPage() {\n if (!context) throw new Error('Browser not launched');\n page = await context.newPage();\n\n // Wire up event handlers\n page.on('response', (response) => {\n const event: ResponseEvent = {\n url: response.url(),\n status: response.status(),\n };\n responseHandlers.forEach((handler) => handler(event));\n });\n\n page.once('close', () => {\n closeHandlers.forEach((handler) => handler());\n });\n },\n\n async goto(url, options) {\n if (!page) throw new Error('Page not created');\n await page\n .goto(url, { timeout: options?.timeout ?? 30000 })\n // Navigation errors are intentionally ignored; caller handles auth via event listeners\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n .catch(() => {});\n },\n\n async getCookies() {\n if (!context) throw new Error('Browser not launched');\n const cookies = await context.cookies();\n return cookies.map(convertCookie);\n },\n\n async clearCookies(domain: string) {\n if (!context) throw new Error('Browser not launched');\n const cookies = await context.cookies();\n const toRemove = cookies.filter(\n (c) =>\n c.domain.includes(domain) ||\n domain.includes(c.domain.replace(/^\\./, '')),\n );\n // Clear each matching cookie individually\n for (const cookie of toRemove) {\n await context.clearCookies({\n name: cookie.name,\n domain: cookie.domain,\n });\n }\n },\n\n async getUserAgent() {\n if (!context) throw new Error('Browser not launched');\n const uaPage = await context.newPage();\n const userAgent = await uaPage.evaluate(() => navigator.userAgent);\n await uaPage.close();\n return userAgent;\n },\n\n async closePage() {\n if (page) {\n await page.close();\n page = null;\n }\n },\n\n async close() {\n if (context) {\n await context.close();\n context = null;\n }\n },\n\n onResponse(handler) {\n responseHandlers.push(handler);\n },\n\n onPageClose(handler) {\n closeHandlers.push(handler);\n },\n };\n}\n","/**\n * Playwright Authentication Plugin\n *\n * Thin wrapper around @abapify/browser-auth core.\n * Provides Playwright-specific browser adapter.\n */\n\nimport { authenticate, testCredentials } from '@abapify/browser-auth';\nimport type {\n BrowserCredentials,\n BrowserAuthOptions,\n} from '@abapify/browser-auth';\nimport { createPlaywrightAdapter } from './adapter';\n\n// Re-export types with Playwright-specific names for backwards compatibility\nexport type PlaywrightCredentials = BrowserCredentials;\nexport type PlaywrightAuthOptions = BrowserAuthOptions;\n\n/**\n * Playwright authentication object\n */\nexport const playwrightAuth = {\n /**\n * Authenticate using Playwright browser\n */\n async authenticate(\n options: PlaywrightAuthOptions,\n ): Promise<PlaywrightCredentials> {\n const adapter = createPlaywrightAdapter();\n return authenticate(adapter, options);\n },\n\n /**\n * Test if credentials are still valid\n */\n test: testCredentials,\n\n /**\n * Refresh is not reliable with Okta SSO - always return null to trigger full re-authentication\n */\n async refresh(session: {\n auth?: { pluginOptions?: { log?: (message: string) => void } };\n }): Promise<PlaywrightCredentials | null> {\n const log = session?.auth?.pluginOptions?.log as\n | ((message: string) => void)\n | undefined;\n (log ?? console.log)(\n '🔄 Session expired, will trigger full re-authentication...',\n );\n return null;\n },\n};\n\n// Re-export utilities\n// Legacy export\nexport const playwright = playwrightAuth;\nexport { toCookieHeader, toHeaders } from '@abapify/browser-auth';\n","/**\n * @fileoverview Playwright Authentication Plugin for AuthManager\n *\n * This module provides the standard auth plugin interface that AuthManager expects.\n * It wraps the Playwright-based browser authentication to return credentials in\n * the format required by the ADT authentication system.\n *\n * @module @abapify/adt-playwright/auth-plugin\n */\n\nimport type {\n AuthPlugin,\n AuthPluginResult,\n AuthPluginOptions,\n AuthSession,\n} from '@abapify/adt-auth';\nimport { playwrightAuth, toCookieHeader } from './playwright-auth';\n\n/** Default session duration when cookies don't specify expiration (8 hours) */\nconst DEFAULT_SESSION_DURATION_MS = 8 * 60 * 60 * 1000;\n\n/**\n * Determines the session expiration time from cookie data.\n *\n * Examines all cookies and returns the earliest expiration time found.\n * If no cookies have expiration set (common with session cookies),\n * returns a default expiration of 8 hours from now.\n *\n * @param cookies - Array of cookie objects with optional expires field\n * @returns The earliest expiration date, or default 8 hours from now\n *\n * @example\n * // Cookie with expiration\n * getExpiresAt([{ expires: 1732900000 }]) // Returns Date from timestamp\n *\n * @example\n * // Session cookie (no expiration)\n * getExpiresAt([{ expires: 0 }]) // Returns Date 8 hours from now\n */\nfunction getExpiresAt(cookies: { expires?: number }[]): Date {\n const expirations = cookies\n .filter((c) => c.expires && c.expires > 0)\n .map((c) => c.expires! * 1000); // Cookie expires is in seconds, convert to ms\n\n if (expirations.length > 0) {\n return new Date(Math.min(...expirations));\n }\n\n return new Date(Date.now() + DEFAULT_SESSION_DURATION_MS);\n}\n\n/**\n * Playwright authentication plugin for AuthManager.\n *\n * Implements the standard AuthPlugin interface to integrate Playwright-based\n * browser authentication with the ADT authentication system.\n *\n * @remarks\n * This plugin:\n * - Opens a browser window for SSO authentication\n * - Captures session cookies after successful login\n * - Converts cookies to the format expected by AuthManager\n * - Does NOT support silent refresh (Okta SSO requires user interaction)\n *\n * @example\n * ```typescript\n * // Used internally by AuthManager when destination type is '@abapify/adt-playwright'\n * import authPlugin from '@abapify/adt-playwright';\n *\n * const result = await authPlugin.authenticate({\n * url: 'https://sap-system.example.com',\n * headless: false,\n * userDataDir: true,\n * });\n * ```\n */\nconst authPlugin: AuthPlugin = {\n /**\n * Authenticate using Playwright browser automation.\n *\n * Opens a browser window, navigates to the SAP system, and waits for\n * the user to complete SSO authentication. Captures cookies and returns\n * them in AuthPluginResult format.\n *\n * @param options - Authentication options including URL and browser settings\n * @returns Promise resolving to credentials in AuthPluginResult format\n */\n async authenticate(options: AuthPluginOptions): Promise<AuthPluginResult> {\n const credentials = await playwrightAuth.authenticate(options);\n const cookieString = toCookieHeader(credentials);\n const expiresAt = getExpiresAt(credentials.cookies);\n\n return {\n method: 'cookie',\n credentials: {\n cookies: cookieString,\n expiresAt,\n },\n };\n },\n\n /**\n * Attempt to refresh an existing session.\n *\n * @remarks\n * Always returns null because Okta SSO sessions cannot be silently refreshed.\n * When a session expires, full re-authentication with user interaction is required.\n * The AuthManager will handle this by calling authenticate() again.\n *\n * @param _session - The existing session (unused)\n * @returns Always null, indicating refresh is not supported\n */\n async refresh(_session: AuthSession): Promise<AuthPluginResult | null> {\n return null;\n },\n};\n\nexport default authPlugin;\n","/**\n * @abapify/adt-playwright\n *\n * Playwright-based SSO authentication plugin for ADT client.\n * Uses @abapify/browser-auth core with Playwright adapter.\n */\n\nimport type { AdtConfig, Destination } from '@abapify/adt-config';\nimport type { BrowserAuthOptions } from '@abapify/browser-auth';\n\n// Re-export types\nexport type {\n PlaywrightCredentials,\n PlaywrightAuthOptions,\n} from './playwright-auth';\nexport type {\n BrowserCredentials,\n BrowserAuthOptions,\n CookieData,\n} from '@abapify/browser-auth';\n\n/**\n * Plugin-level options applied to all destinations\n */\nexport type PlaywrightPluginOptions = BrowserAuthOptions;\n\n/**\n * Create a playwright destination config\n */\nfunction createPlaywrightDestination(options: BrowserAuthOptions): Destination {\n return {\n type: '@abapify/adt-playwright',\n options,\n };\n}\n\n/**\n * Wrap a standard config with playwright auth for all destinations.\n */\nexport function withPlaywright(\n config: AdtConfig,\n options?: Partial<BrowserAuthOptions>,\n): AdtConfig {\n if (!config.destinations) {\n return config;\n }\n\n const destinations: Record<string, Destination> = {};\n\n for (const [name, dest] of Object.entries(config.destinations)) {\n if (typeof dest === 'object' && 'type' in dest) {\n if (\n dest.type === '@abapify/adt-playwright' ||\n dest.type === 'playwright'\n ) {\n destinations[name] = {\n type: '@abapify/adt-playwright',\n options: { ...options, ...(dest.options as BrowserAuthOptions) },\n };\n } else {\n destinations[name] = dest;\n }\n } else if (typeof dest === 'string') {\n destinations[name] = createPlaywrightDestination({\n url: dest,\n ...options,\n });\n } else {\n // Destination-specific options override global options\n destinations[name] = createPlaywrightDestination({\n ...options,\n ...(dest as BrowserAuthOptions),\n });\n }\n }\n\n return { ...config, destinations };\n}\n\n// Main exports\nexport {\n playwrightAuth,\n playwright,\n toCookieHeader,\n toHeaders,\n} from './playwright-auth';\n\n// AuthManager compatibility exports\nexport { default } from './auth-plugin';\nexport { default as authPlugin } from './auth-plugin';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter.ts","../src/playwright-auth.ts","../src/auth-plugin.ts","../src/index.ts"],"sourcesContent":["/**\n * Playwright Browser Adapter\n *\n * Implements BrowserAdapter interface for Playwright.\n * This is a thin wrapper - all auth logic is in @abapify/browser-auth.\n */\n\nimport { chromium } from 'playwright';\nimport type { BrowserContext, Page, Cookie } from 'playwright';\nimport type {\n BrowserAdapter,\n CookieData,\n ResponseEvent,\n} from '@abapify/browser-auth';\n\n/**\n * Convert Playwright cookie to CookieData\n */\nfunction convertCookie(cookie: Cookie): CookieData {\n return {\n name: cookie.name,\n value: cookie.value,\n domain: cookie.domain,\n path: cookie.path,\n expires: cookie.expires,\n httpOnly: cookie.httpOnly,\n secure: cookie.secure,\n sameSite: cookie.sameSite as CookieData['sameSite'],\n };\n}\n\n/**\n * Create a Playwright browser adapter\n */\nexport function createPlaywrightAdapter(): BrowserAdapter {\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n const responseHandlers: ((event: ResponseEvent) => void)[] = [];\n const closeHandlers: (() => void)[] = [];\n\n return {\n async launch(options) {\n const { headless, userDataDir, ignoreHTTPSErrors, userAgent } = options;\n\n if (userDataDir) {\n // Persistent context - stores cookies/session\n context = await chromium.launchPersistentContext(userDataDir, {\n headless,\n ignoreHTTPSErrors,\n userAgent,\n });\n } else {\n const browser = await chromium.launch({ headless });\n context = await browser.newContext({ ignoreHTTPSErrors, userAgent });\n }\n },\n\n async newPage() {\n if (!context) throw new Error('Browser not launched');\n page = await context.newPage();\n\n // Wire up event handlers\n page.on('response', (response) => {\n const event: ResponseEvent = {\n url: response.url(),\n status: response.status(),\n };\n responseHandlers.forEach((handler) => handler(event));\n });\n\n page.once('close', () => {\n closeHandlers.forEach((handler) => handler());\n });\n },\n\n async goto(url, options) {\n if (!page) throw new Error('Page not created');\n await page\n .goto(url, { timeout: options?.timeout ?? 30000 })\n // Navigation errors are intentionally ignored; caller handles auth via event listeners\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n .catch(() => {});\n },\n\n async getCookies() {\n if (!context) throw new Error('Browser not launched');\n const cookies = await context.cookies();\n return cookies.map(convertCookie);\n },\n\n async clearCookies(domain: string) {\n if (!context) throw new Error('Browser not launched');\n const cookies = await context.cookies();\n const toRemove = cookies.filter(\n (c) =>\n c.domain.includes(domain) ||\n domain.includes(c.domain.replace(/^\\./, '')),\n );\n // Clear each matching cookie individually\n for (const cookie of toRemove) {\n await context.clearCookies({\n name: cookie.name,\n domain: cookie.domain,\n });\n }\n },\n\n async getUserAgent() {\n if (!context) throw new Error('Browser not launched');\n const uaPage = await context.newPage();\n const userAgent = await uaPage.evaluate(() => navigator.userAgent);\n await uaPage.close();\n return userAgent;\n },\n\n async closePage() {\n if (page) {\n await page.close();\n page = null;\n }\n },\n\n async close() {\n if (context) {\n await context.close();\n context = null;\n }\n },\n\n onResponse(handler) {\n responseHandlers.push(handler);\n },\n\n onPageClose(handler) {\n closeHandlers.push(handler);\n },\n };\n}\n","/**\n * Playwright Authentication Plugin\n *\n * Thin wrapper around @abapify/browser-auth core.\n * Provides Playwright-specific browser adapter.\n */\n\nimport { authenticate, testCredentials } from '@abapify/browser-auth';\nimport type {\n BrowserCredentials,\n BrowserAuthOptions,\n} from '@abapify/browser-auth';\nimport { createPlaywrightAdapter } from './adapter';\n\n// Re-export types with Playwright-specific names for backwards compatibility\nexport type PlaywrightCredentials = BrowserCredentials;\nexport type PlaywrightAuthOptions = BrowserAuthOptions;\n\n/**\n * Playwright authentication object\n */\nexport const playwrightAuth = {\n /**\n * Authenticate using Playwright browser\n */\n async authenticate(\n options: PlaywrightAuthOptions,\n ): Promise<PlaywrightCredentials> {\n const adapter = createPlaywrightAdapter();\n return authenticate(adapter, options);\n },\n\n /**\n * Test if credentials are still valid\n */\n test: testCredentials,\n\n /**\n * Refresh is not reliable with Okta SSO - always return null to trigger full re-authentication\n */\n async refresh(session: {\n auth?: { pluginOptions?: { log?: (message: string) => void } };\n }): Promise<PlaywrightCredentials | null> {\n const log = session?.auth?.pluginOptions?.log as\n | ((message: string) => void)\n | undefined;\n (log ?? console.log)(\n '🔄 Session expired, will trigger full re-authentication...',\n );\n return null;\n },\n};\n\n// Re-export utilities\n// Legacy export\nexport const playwright = playwrightAuth;\nexport { toCookieHeader, toHeaders } from '@abapify/browser-auth';\n","/**\n * @fileoverview Playwright Authentication Plugin for AuthManager\n *\n * This module provides the standard auth plugin interface that AuthManager expects.\n * It wraps the Playwright-based browser authentication to return credentials in\n * the format required by the ADT authentication system.\n *\n * @module @abapify/adt-playwright/auth-plugin\n */\n\nimport type {\n AuthPlugin,\n AuthPluginResult,\n AuthPluginOptions,\n AuthSession,\n} from '@abapify/adt-auth';\nimport { playwrightAuth, toCookieHeader } from './playwright-auth';\n\n/** Default session duration when cookies don't specify expiration (8 hours) */\nconst DEFAULT_SESSION_DURATION_MS = 8 * 60 * 60 * 1000;\n\n/**\n * Determines the session expiration time from cookie data.\n *\n * Examines all cookies and returns the earliest expiration time found.\n * If no cookies have expiration set (common with session cookies),\n * returns a default expiration of 8 hours from now.\n *\n * @param cookies - Array of cookie objects with optional expires field\n * @returns The earliest expiration date, or default 8 hours from now\n *\n * @example\n * // Cookie with expiration\n * getExpiresAt([{ expires: 1732900000 }]) // Returns Date from timestamp\n *\n * @example\n * // Session cookie (no expiration)\n * getExpiresAt([{ expires: 0 }]) // Returns Date 8 hours from now\n */\nfunction getExpiresAt(cookies: { expires?: number }[]): Date {\n const expirations = cookies\n .filter((c) => c.expires && c.expires > 0)\n .map((c) => c.expires! * 1000); // Cookie expires is in seconds, convert to ms\n\n if (expirations.length > 0) {\n return new Date(Math.min(...expirations));\n }\n\n return new Date(Date.now() + DEFAULT_SESSION_DURATION_MS);\n}\n\n/**\n * Playwright authentication plugin for AuthManager.\n *\n * Implements the standard AuthPlugin interface to integrate Playwright-based\n * browser authentication with the ADT authentication system.\n *\n * @remarks\n * This plugin:\n * - Opens a browser window for SSO authentication\n * - Captures session cookies after successful login\n * - Converts cookies to the format expected by AuthManager\n * - Does NOT support silent refresh (Okta SSO requires user interaction)\n *\n * @example\n * ```typescript\n * // Used internally by AuthManager when destination type is '@abapify/adt-playwright'\n * import authPlugin from '@abapify/adt-playwright';\n *\n * const result = await authPlugin.authenticate({\n * url: 'https://sap-system.example.com',\n * headless: false,\n * userDataDir: true,\n * });\n * ```\n */\nconst authPlugin: AuthPlugin = {\n /**\n * Authenticate using Playwright browser automation.\n *\n * Opens a browser window, navigates to the SAP system, and waits for\n * the user to complete SSO authentication. Captures cookies and returns\n * them in AuthPluginResult format.\n *\n * @param options - Authentication options including URL and browser settings\n * @returns Promise resolving to credentials in AuthPluginResult format\n */\n async authenticate(options: AuthPluginOptions): Promise<AuthPluginResult> {\n const credentials = await playwrightAuth.authenticate(options);\n const cookieString = toCookieHeader(credentials);\n const expiresAt = getExpiresAt(credentials.cookies);\n\n return {\n method: 'cookie',\n credentials: {\n cookies: cookieString,\n expiresAt,\n },\n };\n },\n\n /**\n * Attempt to refresh an existing session.\n *\n * @remarks\n * Always returns null because Okta SSO sessions cannot be silently refreshed.\n * When a session expires, full re-authentication with user interaction is required.\n * The AuthManager will handle this by calling authenticate() again.\n *\n * @param _session - The existing session (unused)\n * @returns Always null, indicating refresh is not supported\n */\n async refresh(_session: AuthSession): Promise<AuthPluginResult | null> {\n return null;\n },\n};\n\nexport default authPlugin;\n","/**\n * @abapify/adt-playwright\n *\n * Playwright-based SSO authentication plugin for ADT client.\n * Uses @abapify/browser-auth core with Playwright adapter.\n */\n\nimport type { AdtConfig, Destination } from '@abapify/adt-config';\nimport type { BrowserAuthOptions } from '@abapify/browser-auth';\n\n// Re-export types\nexport type {\n PlaywrightCredentials,\n PlaywrightAuthOptions,\n} from './playwright-auth';\nexport type {\n BrowserCredentials,\n BrowserAuthOptions,\n CookieData,\n} from '@abapify/browser-auth';\n\n/**\n * Plugin-level options applied to all destinations\n */\nexport type PlaywrightPluginOptions = BrowserAuthOptions;\n\n/**\n * Create a playwright destination config\n */\nfunction createPlaywrightDestination(options: BrowserAuthOptions): Destination {\n return {\n type: '@abapify/adt-playwright',\n options,\n };\n}\n\n/**\n * Wrap a standard config with playwright auth for all destinations.\n */\nexport function withPlaywright(\n config: AdtConfig,\n options?: Partial<BrowserAuthOptions>,\n): AdtConfig {\n if (!config.destinations) {\n return config;\n }\n\n const destinations: Record<string, Destination> = {};\n\n for (const [name, dest] of Object.entries(config.destinations)) {\n if (typeof dest === 'object' && 'type' in dest) {\n if (\n dest.type === '@abapify/adt-playwright' ||\n dest.type === 'playwright'\n ) {\n destinations[name] = {\n type: '@abapify/adt-playwright',\n options: { ...options, ...(dest.options as BrowserAuthOptions) },\n };\n } else {\n destinations[name] = dest;\n }\n } else if (typeof dest === 'string') {\n destinations[name] = createPlaywrightDestination({\n url: dest,\n ...options,\n });\n } else {\n // Destination-specific options override global options\n destinations[name] = createPlaywrightDestination({\n ...options,\n ...(dest as BrowserAuthOptions),\n });\n }\n }\n\n return { ...config, destinations };\n}\n\n// Main exports\nexport {\n playwrightAuth,\n playwright,\n toCookieHeader,\n toHeaders,\n} from './playwright-auth';\n\n// AuthManager compatibility exports\nexport { default } from './auth-plugin';\nexport { default as authPlugin } from './auth-plugin';\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abapify/adt-playwright",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Playwright-based SSO authentication plugin for ADT client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@abapify/adt-auth": "0.4.
|
|
21
|
-
"@abapify/adt-config": "0.4.
|
|
22
|
-
"@abapify/browser-auth": "0.4.
|
|
20
|
+
"@abapify/adt-auth": "0.4.4",
|
|
21
|
+
"@abapify/adt-config": "0.4.4",
|
|
22
|
+
"@abapify/browser-auth": "0.4.4",
|
|
23
23
|
"playwright": "^1.57.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|