latchkey 0.1.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.
- package/.nvmrc +1 -0
- package/.pre-commit-config.yaml +22 -0
- package/.prettierignore +4 -0
- package/.prettierrc +7 -0
- package/CLAUDE.md +13 -0
- package/LICENSE +7 -0
- package/README.md +167 -0
- package/dist/scripts/cryptFile.d.ts +21 -0
- package/dist/scripts/cryptFile.d.ts.map +1 -0
- package/dist/scripts/cryptFile.js +106 -0
- package/dist/scripts/cryptFile.js.map +1 -0
- package/dist/scripts/encryptFile.d.ts +21 -0
- package/dist/scripts/encryptFile.d.ts.map +1 -0
- package/dist/scripts/encryptFile.js +101 -0
- package/dist/scripts/encryptFile.js.map +1 -0
- package/dist/scripts/recordBrowserSession.d.ts +18 -0
- package/dist/scripts/recordBrowserSession.d.ts.map +1 -0
- package/dist/scripts/recordBrowserSession.js +213 -0
- package/dist/scripts/recordBrowserSession.js.map +1 -0
- package/dist/src/apiCredentialStore.d.ts +19 -0
- package/dist/src/apiCredentialStore.d.ts.map +1 -0
- package/dist/src/apiCredentialStore.js +65 -0
- package/dist/src/apiCredentialStore.js.map +1 -0
- package/dist/src/apiCredentials.d.ts +134 -0
- package/dist/src/apiCredentials.d.ts.map +1 -0
- package/dist/src/apiCredentials.js +139 -0
- package/dist/src/apiCredentials.js.map +1 -0
- package/dist/src/browserConfig.d.ts +90 -0
- package/dist/src/browserConfig.d.ts.map +1 -0
- package/dist/src/browserConfig.js +259 -0
- package/dist/src/browserConfig.js.map +1 -0
- package/dist/src/browserState.d.ts +8 -0
- package/dist/src/browserState.d.ts.map +1 -0
- package/dist/src/browserState.js +21 -0
- package/dist/src/browserState.js.map +1 -0
- package/dist/src/cli.d.ts +6 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +25 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/cliCommands.d.ts +29 -0
- package/dist/src/cliCommands.d.ts.map +1 -0
- package/dist/src/cliCommands.js +264 -0
- package/dist/src/cliCommands.js.map +1 -0
- package/dist/src/config.d.ts +35 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +96 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/curl.d.ts +29 -0
- package/dist/src/curl.d.ts.map +1 -0
- package/dist/src/curl.js +53 -0
- package/dist/src/curl.js.map +1 -0
- package/dist/src/encryptedStorage.d.ts +39 -0
- package/dist/src/encryptedStorage.d.ts.map +1 -0
- package/dist/src/encryptedStorage.js +128 -0
- package/dist/src/encryptedStorage.js.map +1 -0
- package/dist/src/encryption.d.ts +28 -0
- package/dist/src/encryption.d.ts.map +1 -0
- package/dist/src/encryption.js +86 -0
- package/dist/src/encryption.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +17 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/keychain.d.ts +33 -0
- package/dist/src/keychain.d.ts.map +1 -0
- package/dist/src/keychain.js +94 -0
- package/dist/src/keychain.js.map +1 -0
- package/dist/src/playwrightUtils.d.ts +27 -0
- package/dist/src/playwrightUtils.d.ts.map +1 -0
- package/dist/src/playwrightUtils.js +122 -0
- package/dist/src/playwrightUtils.js.map +1 -0
- package/dist/src/registry.d.ts +12 -0
- package/dist/src/registry.d.ts.map +1 -0
- package/dist/src/registry.js +30 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/services/base.d.ts +98 -0
- package/dist/src/services/base.d.ts.map +1 -0
- package/dist/src/services/base.js +137 -0
- package/dist/src/services/base.js.map +1 -0
- package/dist/src/services/discord.d.ts +20 -0
- package/dist/src/services/discord.d.ts.map +1 -0
- package/dist/src/services/discord.js +55 -0
- package/dist/src/services/discord.js.map +1 -0
- package/dist/src/services/dropbox.d.ts +23 -0
- package/dist/src/services/dropbox.d.ts.map +1 -0
- package/dist/src/services/dropbox.js +136 -0
- package/dist/src/services/dropbox.js.map +1 -0
- package/dist/src/services/github.d.ts +23 -0
- package/dist/src/services/github.d.ts.map +1 -0
- package/dist/src/services/github.js +110 -0
- package/dist/src/services/github.js.map +1 -0
- package/dist/src/services/index.d.ts +12 -0
- package/dist/src/services/index.d.ts.map +1 -0
- package/dist/src/services/index.js +11 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/linear.d.ts +23 -0
- package/dist/src/services/linear.d.ts.map +1 -0
- package/dist/src/services/linear.js +110 -0
- package/dist/src/services/linear.js.map +1 -0
- package/dist/src/services/slack.d.ts +21 -0
- package/dist/src/services/slack.d.ts.map +1 -0
- package/dist/src/services/slack.js +67 -0
- package/dist/src/services/slack.js.map +1 -0
- package/dist/tests/apiCredentialStore.test.d.ts +2 -0
- package/dist/tests/apiCredentialStore.test.d.ts.map +1 -0
- package/dist/tests/apiCredentialStore.test.js +130 -0
- package/dist/tests/apiCredentialStore.test.js.map +1 -0
- package/dist/tests/apiCredentials.test.d.ts +2 -0
- package/dist/tests/apiCredentials.test.d.ts.map +1 -0
- package/dist/tests/apiCredentials.test.js +169 -0
- package/dist/tests/apiCredentials.test.js.map +1 -0
- package/dist/tests/cli.test.d.ts +2 -0
- package/dist/tests/cli.test.d.ts.map +1 -0
- package/dist/tests/cli.test.js +584 -0
- package/dist/tests/cli.test.js.map +1 -0
- package/dist/tests/encryptedStorage.test.d.ts +2 -0
- package/dist/tests/encryptedStorage.test.d.ts.map +1 -0
- package/dist/tests/encryptedStorage.test.js +126 -0
- package/dist/tests/encryptedStorage.test.js.map +1 -0
- package/dist/tests/encryption.test.d.ts +2 -0
- package/dist/tests/encryption.test.d.ts.map +1 -0
- package/dist/tests/encryption.test.js +121 -0
- package/dist/tests/encryption.test.js.map +1 -0
- package/dist/tests/lint.test.d.ts +2 -0
- package/dist/tests/lint.test.d.ts.map +1 -0
- package/dist/tests/lint.test.js +18 -0
- package/dist/tests/lint.test.js.map +1 -0
- package/dist/tests/registry.test.d.ts +2 -0
- package/dist/tests/registry.test.d.ts.map +1 -0
- package/dist/tests/registry.test.js +85 -0
- package/dist/tests/registry.test.js.map +1 -0
- package/dist/tests/servicesAgainstRecordings.test.d.ts +20 -0
- package/dist/tests/servicesAgainstRecordings.test.d.ts.map +1 -0
- package/dist/tests/servicesAgainstRecordings.test.js +157 -0
- package/dist/tests/servicesAgainstRecordings.test.js.map +1 -0
- package/dist/tests/typecheck.test.d.ts +2 -0
- package/dist/tests/typecheck.test.d.ts.map +1 -0
- package/dist/tests/typecheck.test.js +18 -0
- package/dist/tests/typecheck.test.js.map +1 -0
- package/docs/development.md +94 -0
- package/eslint.config.js +30 -0
- package/integrations/SKILL.md +62 -0
- package/package.json +68 -0
- package/scripts/cryptFile.ts +123 -0
- package/scripts/recordBrowserSession.ts +280 -0
- package/scripts/tsconfig.json +10 -0
- package/src/apiCredentialStore.ts +87 -0
- package/src/apiCredentials.ts +180 -0
- package/src/cli.ts +32 -0
- package/src/cliCommands.ts +321 -0
- package/src/config.ts +115 -0
- package/src/curl.ts +78 -0
- package/src/encryptedStorage.ts +161 -0
- package/src/encryption.ts +106 -0
- package/src/index.ts +65 -0
- package/src/keychain.ts +105 -0
- package/src/playwrightUtils.ts +143 -0
- package/src/registry.ts +35 -0
- package/src/services/base.ts +234 -0
- package/src/services/discord.ts +73 -0
- package/src/services/dropbox.ts +173 -0
- package/src/services/github.ts +139 -0
- package/src/services/index.ts +13 -0
- package/src/services/linear.ts +134 -0
- package/src/services/slack.ts +85 -0
- package/tests/apiCredentialStore.test.ts +162 -0
- package/tests/apiCredentials.test.ts +195 -0
- package/tests/cli.test.ts +798 -0
- package/tests/encryptedStorage.test.ts +173 -0
- package/tests/encryption.test.ts +169 -0
- package/tests/lint.test.ts +19 -0
- package/tests/registry.test.ts +103 -0
- package/tests/servicesAgainstRecordings.test.ts +230 -0
- package/tests/typecheck.test.ts +19 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAW,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAEvF,MAAM,OAAO,QAAQ;IACV,QAAQ,CAAqB;IAEtC,YAAY,QAA4B;QACtC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC1B,OAAO,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,CAAC,GAAW;QAClB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;gBAC7C,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC/B,OAAO,OAAO,CAAC;gBACjB,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base classes and interfaces for service implementations.
|
|
3
|
+
*/
|
|
4
|
+
import type { Browser, BrowserContext, Response } from 'playwright';
|
|
5
|
+
import { ApiCredentialStatus, ApiCredentials } from '../apiCredentials.js';
|
|
6
|
+
import { EncryptedStorage } from '../encryptedStorage.js';
|
|
7
|
+
export declare class LoginCancelledError extends Error {
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class LoginFailedError extends Error {
|
|
11
|
+
constructor(message?: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Base interface for a service that latchkey can authenticate with.
|
|
15
|
+
*/
|
|
16
|
+
export interface Service {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly baseApiUrls: readonly string[];
|
|
19
|
+
readonly loginUrl: string;
|
|
20
|
+
/**
|
|
21
|
+
* Check if the given API credentials are valid for this service.
|
|
22
|
+
*/
|
|
23
|
+
checkApiCredentials(apiCredentials: ApiCredentials): ApiCredentialStatus;
|
|
24
|
+
/**
|
|
25
|
+
* Return curl arguments for checking credentials (excluding auth headers).
|
|
26
|
+
*/
|
|
27
|
+
readonly credentialCheckCurlArguments: readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* Get a new session for the login flow.
|
|
30
|
+
*/
|
|
31
|
+
getSession(): ServiceSession;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Base class for service sessions that handle the login flow.
|
|
35
|
+
*/
|
|
36
|
+
export declare abstract class ServiceSession {
|
|
37
|
+
readonly service: Service;
|
|
38
|
+
constructor(service: Service);
|
|
39
|
+
/**
|
|
40
|
+
* Handle a response during the headful login phase.
|
|
41
|
+
*/
|
|
42
|
+
abstract onResponse(response: Response): void;
|
|
43
|
+
/**
|
|
44
|
+
* Check if the login phase is complete.
|
|
45
|
+
*/
|
|
46
|
+
protected abstract isLoginComplete(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Finalize credentials after the headful login phase.
|
|
49
|
+
* Receives the browser and context from the login phase, which are still open.
|
|
50
|
+
*/
|
|
51
|
+
protected abstract finalizeCredentials(browser: Browser, context: BrowserContext): Promise<ApiCredentials | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Wait until the browser login phase is complete.
|
|
54
|
+
*/
|
|
55
|
+
private waitForLoginComplete;
|
|
56
|
+
/**
|
|
57
|
+
* Optionally diagnose a timeout error that occurred during credential finalization.
|
|
58
|
+
*
|
|
59
|
+
* Services can override this to inspect the page state and return a more
|
|
60
|
+
* specific error (e.g., checking for permission denied messages).
|
|
61
|
+
* If this returns an error, it will be thrown instead of the generic
|
|
62
|
+
* LoginFailedError. If it returns null, the original timeout error message is used.
|
|
63
|
+
*/
|
|
64
|
+
protected diagnoseTimeoutError(_context: BrowserContext, _originalError: Error): Promise<Error | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Perform the login flow and return the extracted credentials.
|
|
67
|
+
*/
|
|
68
|
+
login(encryptedStorage: EncryptedStorage, browserStatePath: string): Promise<ApiCredentials>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Simple service session where credentials are extracted by observing requests during login.
|
|
72
|
+
*/
|
|
73
|
+
export declare abstract class SimpleServiceSession extends ServiceSession {
|
|
74
|
+
protected apiCredentials: ApiCredentials | null;
|
|
75
|
+
/**
|
|
76
|
+
* Extract API credentials from a response during the headful login phase.
|
|
77
|
+
*/
|
|
78
|
+
protected abstract getApiCredentialsFromResponse(response: Response): Promise<ApiCredentials | null>;
|
|
79
|
+
onResponse(response: Response): void;
|
|
80
|
+
protected isLoginComplete(): boolean;
|
|
81
|
+
protected finalizeCredentials(_browser: Browser, _context: BrowserContext): Promise<ApiCredentials | null>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Service session that requires a browser followup to finalize credentials.
|
|
85
|
+
*
|
|
86
|
+
* The login phase captures login state. After login completes,
|
|
87
|
+
* the same browser session is reused to perform additional actions
|
|
88
|
+
* (e.g., navigating to settings and creating an API key).
|
|
89
|
+
*/
|
|
90
|
+
export declare abstract class BrowserFollowupServiceSession extends ServiceSession {
|
|
91
|
+
/**
|
|
92
|
+
* Perform actions in the browser to finalize and extract API credentials.
|
|
93
|
+
* This runs in the same browser session used for login.
|
|
94
|
+
*/
|
|
95
|
+
protected abstract performBrowserFollowup(context: BrowserContext): Promise<ApiCredentials | null>;
|
|
96
|
+
protected finalizeCredentials(_browser: Browser, context: BrowserContext): Promise<ApiCredentials | null>;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/services/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAQ,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,SAAwD;CAI5E;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,SAAiD;CAIrE;AAiBD;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,mBAAmB,CAAC,cAAc,EAAE,cAAc,GAAG,mBAAmB,CAAC;IAEzE;;OAEG;IACH,QAAQ,CAAC,4BAA4B,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzD;;OAEG;IACH,UAAU,IAAI,cAAc,CAAC;CAC9B;AAED;;GAEG;AACH,8BAAsB,cAAc;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,OAAO,EAAE,OAAO;IAI5B;;OAEG;IACH,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAE7C;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,IAAI,OAAO;IAE7C;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,mBAAmB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAEjC;;OAEG;YACW,oBAAoB;IAOlC;;;;;;;OAOG;IACH,SAAS,CAAC,oBAAoB,CAC5B,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,KAAK,GACpB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAIxB;;OAEG;IACG,KAAK,CACT,gBAAgB,EAAE,gBAAgB,EAClC,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,cAAc,CAAC;CA8C3B;AAED;;GAEG;AACH,8BAAsB,oBAAqB,SAAQ,cAAc;IAC/D,SAAS,CAAC,cAAc,EAAE,cAAc,GAAG,IAAI,CAAQ;IAEvD;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,6BAA6B,CAC9C,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAEjC,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAepC,SAAS,CAAC,eAAe,IAAI,OAAO;IAIpC,SAAS,CAAC,mBAAmB,CAC3B,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;CAGlC;AAED;;;;;;GAMG;AACH,8BAAsB,6BAA8B,SAAQ,cAAc;IACxE;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,sBAAsB,CACvC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;cAER,mBAAmB,CAC1C,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;CAIlC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base classes and interfaces for service implementations.
|
|
3
|
+
*/
|
|
4
|
+
import { showSpinnerPage, withTempBrowserContext } from '../playwrightUtils.js';
|
|
5
|
+
export class LoginCancelledError extends Error {
|
|
6
|
+
constructor(message = 'Login was cancelled because the browser was closed.') {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'LoginCancelledError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class LoginFailedError extends Error {
|
|
12
|
+
constructor(message = 'Login failed: no credentials were extracted.') {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'LoginFailedError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isBrowserClosedError(error) {
|
|
18
|
+
const message = error.message.toLowerCase();
|
|
19
|
+
return (message.includes('target closed') ||
|
|
20
|
+
message.includes('browser closed') ||
|
|
21
|
+
message.includes('browser has been closed') ||
|
|
22
|
+
message.includes('context has been closed') ||
|
|
23
|
+
message.includes('page has been closed'));
|
|
24
|
+
}
|
|
25
|
+
function isTimeoutError(error) {
|
|
26
|
+
return error.name === 'TimeoutError';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Base class for service sessions that handle the login flow.
|
|
30
|
+
*/
|
|
31
|
+
export class ServiceSession {
|
|
32
|
+
service;
|
|
33
|
+
constructor(service) {
|
|
34
|
+
this.service = service;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Wait until the browser login phase is complete.
|
|
38
|
+
*/
|
|
39
|
+
async waitForLoginComplete(page) {
|
|
40
|
+
while (!this.isLoginComplete()) {
|
|
41
|
+
await page.waitForTimeout(100);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Optionally diagnose a timeout error that occurred during credential finalization.
|
|
46
|
+
*
|
|
47
|
+
* Services can override this to inspect the page state and return a more
|
|
48
|
+
* specific error (e.g., checking for permission denied messages).
|
|
49
|
+
* If this returns an error, it will be thrown instead of the generic
|
|
50
|
+
* LoginFailedError. If it returns null, the original timeout error message is used.
|
|
51
|
+
*/
|
|
52
|
+
diagnoseTimeoutError(_context, _originalError) {
|
|
53
|
+
return Promise.resolve(null);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Perform the login flow and return the extracted credentials.
|
|
57
|
+
*/
|
|
58
|
+
async login(encryptedStorage, browserStatePath) {
|
|
59
|
+
return withTempBrowserContext(encryptedStorage, browserStatePath, async ({ browser, context }) => {
|
|
60
|
+
const page = await context.newPage();
|
|
61
|
+
page.on('response', (response) => {
|
|
62
|
+
this.onResponse(response);
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
await page.goto(this.service.loginUrl);
|
|
66
|
+
await this.waitForLoginComplete(page);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error instanceof Error && isBrowserClosedError(error)) {
|
|
70
|
+
throw new LoginCancelledError();
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
let apiCredentials;
|
|
75
|
+
try {
|
|
76
|
+
apiCredentials = await this.finalizeCredentials(browser, context);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof Error && isBrowserClosedError(error)) {
|
|
80
|
+
throw new LoginCancelledError();
|
|
81
|
+
}
|
|
82
|
+
if (error instanceof Error && isTimeoutError(error)) {
|
|
83
|
+
const diagnosedError = await this.diagnoseTimeoutError(context, error);
|
|
84
|
+
if (diagnosedError !== null) {
|
|
85
|
+
throw diagnosedError;
|
|
86
|
+
}
|
|
87
|
+
throw new LoginFailedError(`Login failed: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
if (apiCredentials === null) {
|
|
92
|
+
throw new LoginFailedError();
|
|
93
|
+
}
|
|
94
|
+
return apiCredentials;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Simple service session where credentials are extracted by observing requests during login.
|
|
100
|
+
*/
|
|
101
|
+
export class SimpleServiceSession extends ServiceSession {
|
|
102
|
+
apiCredentials = null;
|
|
103
|
+
onResponse(response) {
|
|
104
|
+
if (this.apiCredentials !== null) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.getApiCredentialsFromResponse(response)
|
|
108
|
+
.then((credentials) => {
|
|
109
|
+
if (this.apiCredentials === null && credentials !== null) {
|
|
110
|
+
this.apiCredentials = credentials;
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
.catch(() => {
|
|
114
|
+
// Ignore errors extracting credentials
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
isLoginComplete() {
|
|
118
|
+
return this.apiCredentials !== null;
|
|
119
|
+
}
|
|
120
|
+
finalizeCredentials(_browser, _context) {
|
|
121
|
+
return Promise.resolve(this.apiCredentials);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Service session that requires a browser followup to finalize credentials.
|
|
126
|
+
*
|
|
127
|
+
* The login phase captures login state. After login completes,
|
|
128
|
+
* the same browser session is reused to perform additional actions
|
|
129
|
+
* (e.g., navigating to settings and creating an API key).
|
|
130
|
+
*/
|
|
131
|
+
export class BrowserFollowupServiceSession extends ServiceSession {
|
|
132
|
+
async finalizeCredentials(_browser, context) {
|
|
133
|
+
await showSpinnerPage(context, this.service.name);
|
|
134
|
+
return this.performBrowserFollowup(context);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/services/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord service implementation.
|
|
3
|
+
*/
|
|
4
|
+
import type { Response } from 'playwright';
|
|
5
|
+
import { ApiCredentialStatus, ApiCredentials } from '../apiCredentials.js';
|
|
6
|
+
import { Service, SimpleServiceSession } from './base.js';
|
|
7
|
+
declare class DiscordServiceSession extends SimpleServiceSession {
|
|
8
|
+
protected getApiCredentialsFromResponse(response: Response): Promise<ApiCredentials | null>;
|
|
9
|
+
}
|
|
10
|
+
export declare class Discord implements Service {
|
|
11
|
+
readonly name = "discord";
|
|
12
|
+
readonly baseApiUrls: readonly ["https://discord.com/api/"];
|
|
13
|
+
readonly loginUrl = "https://discord.com/login";
|
|
14
|
+
readonly credentialCheckCurlArguments: readonly ["https://discord.com/api/v9/users/@me"];
|
|
15
|
+
getSession(): DiscordServiceSession;
|
|
16
|
+
checkApiCredentials(apiCredentials: ApiCredentials): ApiCredentialStatus;
|
|
17
|
+
}
|
|
18
|
+
export declare const DISCORD: Discord;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=discord.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.d.ts","sourceRoot":"","sources":["../../../src/services/discord.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAqB,MAAM,sBAAsB,CAAC;AAE9F,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAE1D,cAAM,qBAAsB,SAAQ,oBAAoB;cACtC,6BAA6B,CAC3C,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;CAsBlC;AAED,qBAAa,OAAQ,YAAW,OAAO;IACrC,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,WAAW,wCAAyC;IAC7D,QAAQ,CAAC,QAAQ,+BAA+B;IAEhD,QAAQ,CAAC,4BAA4B,oDAAqD;IAE1F,UAAU,IAAI,qBAAqB;IAInC,mBAAmB,CAAC,cAAc,EAAE,cAAc,GAAG,mBAAmB;CAuBzE;AAED,eAAO,MAAM,OAAO,SAAgB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord service implementation.
|
|
3
|
+
*/
|
|
4
|
+
import { ApiCredentialStatus, AuthorizationBare } from '../apiCredentials.js';
|
|
5
|
+
import { runCaptured } from '../curl.js';
|
|
6
|
+
import { SimpleServiceSession } from './base.js';
|
|
7
|
+
class DiscordServiceSession extends SimpleServiceSession {
|
|
8
|
+
async getApiCredentialsFromResponse(response) {
|
|
9
|
+
const request = response.request();
|
|
10
|
+
const url = request.url();
|
|
11
|
+
if (!url.startsWith('https://discord.com/api/')) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
// Require 2XX response to ensure the session is valid (not expired)
|
|
15
|
+
const status = response.status();
|
|
16
|
+
if (status < 200 || status >= 300) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const headers = await request.allHeaders();
|
|
20
|
+
const authorization = headers.authorization;
|
|
21
|
+
if (authorization !== undefined && authorization.trim() !== '') {
|
|
22
|
+
return new AuthorizationBare(authorization);
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class Discord {
|
|
28
|
+
name = 'discord';
|
|
29
|
+
baseApiUrls = ['https://discord.com/api/'];
|
|
30
|
+
loginUrl = 'https://discord.com/login';
|
|
31
|
+
credentialCheckCurlArguments = ['https://discord.com/api/v9/users/@me'];
|
|
32
|
+
getSession() {
|
|
33
|
+
return new DiscordServiceSession(this);
|
|
34
|
+
}
|
|
35
|
+
checkApiCredentials(apiCredentials) {
|
|
36
|
+
if (!(apiCredentials instanceof AuthorizationBare)) {
|
|
37
|
+
return ApiCredentialStatus.Invalid;
|
|
38
|
+
}
|
|
39
|
+
const result = runCaptured([
|
|
40
|
+
'-s',
|
|
41
|
+
'-o',
|
|
42
|
+
'/dev/null',
|
|
43
|
+
'-w',
|
|
44
|
+
'%{http_code}',
|
|
45
|
+
...apiCredentials.asCurlArguments(),
|
|
46
|
+
...this.credentialCheckCurlArguments,
|
|
47
|
+
], 10);
|
|
48
|
+
if (result.stdout === '200') {
|
|
49
|
+
return ApiCredentialStatus.Valid;
|
|
50
|
+
}
|
|
51
|
+
return ApiCredentialStatus.Invalid;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export const DISCORD = new Discord();
|
|
55
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../../../src/services/discord.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,mBAAmB,EAAkB,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAW,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAE1D,MAAM,qBAAsB,SAAQ,oBAAoB;IAC5C,KAAK,CAAC,6BAA6B,CAC3C,QAAkB;QAElB,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAE1B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,0BAA0B,CAAC,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,oEAAoE;QACpE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,MAAM,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC3C,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC5C,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC/D,OAAO,IAAI,iBAAiB,CAAC,aAAa,CAAC,CAAC;QAC9C,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,MAAM,OAAO,OAAO;IACT,IAAI,GAAG,SAAS,CAAC;IACjB,WAAW,GAAG,CAAC,0BAA0B,CAAU,CAAC;IACpD,QAAQ,GAAG,2BAA2B,CAAC;IAEvC,4BAA4B,GAAG,CAAC,sCAAsC,CAAU,CAAC;IAE1F,UAAU;QACR,OAAO,IAAI,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,mBAAmB,CAAC,cAA8B;QAChD,IAAI,CAAC,CAAC,cAAc,YAAY,iBAAiB,CAAC,EAAE,CAAC;YACnD,OAAO,mBAAmB,CAAC,OAAO,CAAC;QACrC,CAAC;QAED,MAAM,MAAM,GAAG,WAAW,CACxB;YACE,IAAI;YACJ,IAAI;YACJ,WAAW;YACX,IAAI;YACJ,cAAc;YACd,GAAG,cAAc,CAAC,eAAe,EAAE;YACnC,GAAG,IAAI,CAAC,4BAA4B;SACrC,EACD,EAAE,CACH,CAAC;QAEF,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC5B,OAAO,mBAAmB,CAAC,KAAK,CAAC;QACnC,CAAC;QACD,OAAO,mBAAmB,CAAC,OAAO,CAAC;IACrC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropbox service implementation.
|
|
3
|
+
*/
|
|
4
|
+
import type { Response, BrowserContext } from 'playwright';
|
|
5
|
+
import { ApiCredentialStatus, ApiCredentials } from '../apiCredentials.js';
|
|
6
|
+
import { Service, BrowserFollowupServiceSession } from './base.js';
|
|
7
|
+
declare class DropboxServiceSession extends BrowserFollowupServiceSession {
|
|
8
|
+
private isLoggedIn;
|
|
9
|
+
onResponse(response: Response): void;
|
|
10
|
+
protected isLoginComplete(): boolean;
|
|
11
|
+
protected performBrowserFollowup(context: BrowserContext): Promise<ApiCredentials | null>;
|
|
12
|
+
}
|
|
13
|
+
export declare class Dropbox implements Service {
|
|
14
|
+
readonly name = "dropbox";
|
|
15
|
+
readonly baseApiUrls: readonly ["https://api.dropboxapi.com/", "https://content.dropboxapi.com/", "https://notify.dropboxapi.com/"];
|
|
16
|
+
readonly loginUrl = "https://www.dropbox.com/login";
|
|
17
|
+
readonly credentialCheckCurlArguments: readonly ["-X", "POST", "https://api.dropboxapi.com/2/users/get_current_account"];
|
|
18
|
+
getSession(): DropboxServiceSession;
|
|
19
|
+
checkApiCredentials(apiCredentials: ApiCredentials): ApiCredentialStatus;
|
|
20
|
+
}
|
|
21
|
+
export declare const DROPBOX: Dropbox;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=dropbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropbox.d.ts","sourceRoot":"","sources":["../../../src/services/dropbox.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAuB,MAAM,sBAAsB,CAAC;AAGhG,OAAO,EAAE,OAAO,EAAE,6BAA6B,EAAoB,MAAM,WAAW,CAAC;AAIrF,cAAM,qBAAsB,SAAQ,6BAA6B;IAC/D,OAAO,CAAC,UAAU,CAAS;IAE3B,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IA0BpC,SAAS,CAAC,eAAe,IAAI,OAAO;cAIpB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;CAgFhG;AAED,qBAAa,OAAQ,YAAW,OAAO;IACrC,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,WAAW,gHAIT;IACX,QAAQ,CAAC,QAAQ,mCAAmC;IAEpD,QAAQ,CAAC,4BAA4B,oFAI1B;IAEX,UAAU,IAAI,qBAAqB;IAInC,mBAAmB,CAAC,cAAc,EAAE,cAAc,GAAG,mBAAmB;CAuBzE;AAED,eAAO,MAAM,OAAO,SAAgB,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropbox service implementation.
|
|
3
|
+
*/
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { ApiCredentialStatus, AuthorizationBearer } from '../apiCredentials.js';
|
|
6
|
+
import { runCaptured } from '../curl.js';
|
|
7
|
+
import { typeLikeHuman } from '../playwrightUtils.js';
|
|
8
|
+
import { BrowserFollowupServiceSession, LoginFailedError } from './base.js';
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 8000;
|
|
10
|
+
class DropboxServiceSession extends BrowserFollowupServiceSession {
|
|
11
|
+
isLoggedIn = false;
|
|
12
|
+
onResponse(response) {
|
|
13
|
+
if (this.isLoggedIn) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const request = response.request();
|
|
17
|
+
const url = request.url();
|
|
18
|
+
if (!url.startsWith('https://www.dropbox.com/')) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Require 2XX response to ensure the session is valid (not expired)
|
|
22
|
+
const status = response.status();
|
|
23
|
+
if (status < 200 || status >= 300) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const headers = request.headers();
|
|
27
|
+
const uidHeader = headers['x-dropbox-uid'];
|
|
28
|
+
if (uidHeader === undefined || uidHeader === '-1') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.isLoggedIn = true;
|
|
32
|
+
}
|
|
33
|
+
isLoginComplete() {
|
|
34
|
+
return this.isLoggedIn;
|
|
35
|
+
}
|
|
36
|
+
async performBrowserFollowup(context) {
|
|
37
|
+
const page = context.pages()[0];
|
|
38
|
+
if (!page) {
|
|
39
|
+
throw new LoginFailedError('No page available in browser context.');
|
|
40
|
+
}
|
|
41
|
+
await page.goto('https://www.dropbox.com/developers/apps/create');
|
|
42
|
+
const scopedInput = page.locator('input#scoped');
|
|
43
|
+
await scopedInput.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
44
|
+
await scopedInput.click();
|
|
45
|
+
const fullPermissionsInput = page.locator('input#full_permissions');
|
|
46
|
+
await fullPermissionsInput.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
47
|
+
await fullPermissionsInput.click();
|
|
48
|
+
const appName = `Latchkey-${randomUUID().slice(0, 8)}`;
|
|
49
|
+
const appNameInput = page.locator('input#app-name');
|
|
50
|
+
await appNameInput.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
51
|
+
await typeLikeHuman(page, appNameInput, appName);
|
|
52
|
+
const createButton = page.getByRole('button', { name: 'Create app' });
|
|
53
|
+
await createButton.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
54
|
+
await createButton.click();
|
|
55
|
+
await page.waitForURL(/https:\/\/www\.dropbox\.com\/developers\/apps\/info\//, {
|
|
56
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
57
|
+
});
|
|
58
|
+
// Configure permissions before generating token
|
|
59
|
+
const permissionsTab = page.locator('a.c-tabs__label[data-hash="permissions"]');
|
|
60
|
+
await permissionsTab.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
61
|
+
await permissionsTab.click();
|
|
62
|
+
// Enable all necessary permissions
|
|
63
|
+
const permissionIds = [
|
|
64
|
+
'files.metadata.write',
|
|
65
|
+
'files.content.write',
|
|
66
|
+
'files.content.read',
|
|
67
|
+
'sharing.write',
|
|
68
|
+
'file_requests.write',
|
|
69
|
+
'contacts.write',
|
|
70
|
+
];
|
|
71
|
+
for (const permissionId of permissionIds) {
|
|
72
|
+
const escapedPermissionId = permissionId.replace(/\./g, '\\.');
|
|
73
|
+
const checkbox = page.locator(`input#${escapedPermissionId}`);
|
|
74
|
+
await checkbox.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
75
|
+
await checkbox.click();
|
|
76
|
+
}
|
|
77
|
+
// Submit permissions
|
|
78
|
+
const submitButton = page.locator('button.permissions-submit-button');
|
|
79
|
+
await submitButton.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
80
|
+
await submitButton.click();
|
|
81
|
+
// Wait for permissions to be saved
|
|
82
|
+
await page.waitForTimeout(512);
|
|
83
|
+
// Return to Settings tab to generate token
|
|
84
|
+
const settingsTab = page.locator('a.c-tabs__label[data-hash="settings"]');
|
|
85
|
+
await settingsTab.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
86
|
+
await settingsTab.click();
|
|
87
|
+
const generateButton = page.locator('input#generate-token-button');
|
|
88
|
+
await generateButton.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
89
|
+
await generateButton.click();
|
|
90
|
+
const tokenInput = page.locator('input#generated-token[data-token]');
|
|
91
|
+
await tokenInput.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
92
|
+
const token = await tokenInput.getAttribute('data-token');
|
|
93
|
+
if (token === null || token === '') {
|
|
94
|
+
throw new LoginFailedError('Failed to extract token from Dropbox.');
|
|
95
|
+
}
|
|
96
|
+
await page.close();
|
|
97
|
+
return new AuthorizationBearer(token);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export class Dropbox {
|
|
101
|
+
name = 'dropbox';
|
|
102
|
+
baseApiUrls = [
|
|
103
|
+
'https://api.dropboxapi.com/',
|
|
104
|
+
'https://content.dropboxapi.com/',
|
|
105
|
+
'https://notify.dropboxapi.com/',
|
|
106
|
+
];
|
|
107
|
+
loginUrl = 'https://www.dropbox.com/login';
|
|
108
|
+
credentialCheckCurlArguments = [
|
|
109
|
+
'-X',
|
|
110
|
+
'POST',
|
|
111
|
+
'https://api.dropboxapi.com/2/users/get_current_account',
|
|
112
|
+
];
|
|
113
|
+
getSession() {
|
|
114
|
+
return new DropboxServiceSession(this);
|
|
115
|
+
}
|
|
116
|
+
checkApiCredentials(apiCredentials) {
|
|
117
|
+
if (!(apiCredentials instanceof AuthorizationBearer)) {
|
|
118
|
+
return ApiCredentialStatus.Invalid;
|
|
119
|
+
}
|
|
120
|
+
const result = runCaptured([
|
|
121
|
+
'-s',
|
|
122
|
+
'-o',
|
|
123
|
+
'/dev/null',
|
|
124
|
+
'-w',
|
|
125
|
+
'%{http_code}',
|
|
126
|
+
...apiCredentials.asCurlArguments(),
|
|
127
|
+
...this.credentialCheckCurlArguments,
|
|
128
|
+
], 10);
|
|
129
|
+
if (result.stdout === '200') {
|
|
130
|
+
return ApiCredentialStatus.Valid;
|
|
131
|
+
}
|
|
132
|
+
return ApiCredentialStatus.Invalid;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export const DROPBOX = new Dropbox();
|
|
136
|
+
//# sourceMappingURL=dropbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropbox.js","sourceRoot":"","sources":["../../../src/services/dropbox.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAkB,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAW,6BAA6B,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAErF,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,MAAM,qBAAsB,SAAQ,6BAA6B;IACvD,UAAU,GAAG,KAAK,CAAC;IAE3B,UAAU,CAAC,QAAkB;QAC3B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,0BAA0B,CAAC,EAAE,CAAC;YAChD,OAAO;QACT,CAAC;QAED,oEAAoE;QACpE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,MAAM,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;QAC3C,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YAClD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAES,eAAe;QACvB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAES,KAAK,CAAC,sBAAsB,CAAC,OAAuB;QAC5D,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,gBAAgB,CAAC,uCAAuC,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,IAAI,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAElE,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACjD,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,MAAM,WAAW,CAAC,KAAK,EAAE,CAAC;QAE1B,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;QACpE,MAAM,oBAAoB,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACpE,MAAM,oBAAoB,CAAC,KAAK,EAAE,CAAC;QAEnC,MAAM,OAAO,GAAG,YAAY,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACpD,MAAM,YAAY,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC5D,MAAM,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;QAEjD,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QACtE,MAAM,YAAY,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC5D,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;QAE3B,MAAM,IAAI,CAAC,UAAU,CAAC,uDAAuD,EAAE;YAC7E,OAAO,EAAE,kBAAkB;SAC5B,CAAC,CAAC;QAEH,gDAAgD;QAChD,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC;QAChF,MAAM,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC9D,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;QAE7B,mCAAmC;QACnC,MAAM,aAAa,GAAG;YACpB,sBAAsB;YACtB,qBAAqB;YACrB,oBAAoB;YACpB,eAAe;YACf,qBAAqB;YACrB,gBAAgB;SACjB,CAAC;QAEF,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;YACzC,MAAM,mBAAmB,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAAC;YAC9D,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;YACxD,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;QAED,qBAAqB;QACrB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC;QACtE,MAAM,YAAY,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC5D,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;QAE3B,mCAAmC;QACnC,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAE/B,2CAA2C;QAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC;QAC1E,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,MAAM,WAAW,CAAC,KAAK,EAAE,CAAC;QAE1B,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;QACnE,MAAM,cAAc,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC9D,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;QAE7B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC;QACrE,MAAM,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAE1D,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAC1D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,gBAAgB,CAAC,uCAAuC,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QAEnB,OAAO,IAAI,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;CACF;AAED,MAAM,OAAO,OAAO;IACT,IAAI,GAAG,SAAS,CAAC;IACjB,WAAW,GAAG;QACrB,6BAA6B;QAC7B,iCAAiC;QACjC,gCAAgC;KACxB,CAAC;IACF,QAAQ,GAAG,+BAA+B,CAAC;IAE3C,4BAA4B,GAAG;QACtC,IAAI;QACJ,MAAM;QACN,wDAAwD;KAChD,CAAC;IAEX,UAAU;QACR,OAAO,IAAI,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,mBAAmB,CAAC,cAA8B;QAChD,IAAI,CAAC,CAAC,cAAc,YAAY,mBAAmB,CAAC,EAAE,CAAC;YACrD,OAAO,mBAAmB,CAAC,OAAO,CAAC;QACrC,CAAC;QAED,MAAM,MAAM,GAAG,WAAW,CACxB;YACE,IAAI;YACJ,IAAI;YACJ,WAAW;YACX,IAAI;YACJ,cAAc;YACd,GAAG,cAAc,CAAC,eAAe,EAAE;YACnC,GAAG,IAAI,CAAC,4BAA4B;SACrC,EACD,EAAE,CACH,CAAC;QAEF,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC5B,OAAO,mBAAmB,CAAC,KAAK,CAAC;QACnC,CAAC;QACD,OAAO,mBAAmB,CAAC,OAAO,CAAC;IACrC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub service implementation.
|
|
3
|
+
*/
|
|
4
|
+
import type { Response, BrowserContext } from 'playwright';
|
|
5
|
+
import { ApiCredentialStatus, ApiCredentials } from '../apiCredentials.js';
|
|
6
|
+
import { Service, BrowserFollowupServiceSession } from './base.js';
|
|
7
|
+
declare class GithubServiceSession extends BrowserFollowupServiceSession {
|
|
8
|
+
private isLoggedIn;
|
|
9
|
+
onResponse(response: Response): void;
|
|
10
|
+
protected isLoginComplete(): boolean;
|
|
11
|
+
protected performBrowserFollowup(context: BrowserContext): Promise<ApiCredentials | null>;
|
|
12
|
+
}
|
|
13
|
+
export declare class Github implements Service {
|
|
14
|
+
readonly name = "github";
|
|
15
|
+
readonly baseApiUrls: readonly ["https://api.github.com/"];
|
|
16
|
+
readonly loginUrl = "https://github.com/settings/tokens/new";
|
|
17
|
+
readonly credentialCheckCurlArguments: readonly ["https://api.github.com/user"];
|
|
18
|
+
getSession(): GithubServiceSession;
|
|
19
|
+
checkApiCredentials(apiCredentials: ApiCredentials): ApiCredentialStatus;
|
|
20
|
+
}
|
|
21
|
+
export declare const GITHUB: Github;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../src/services/github.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAuB,MAAM,sBAAsB,CAAC;AAGhG,OAAO,EAAE,OAAO,EAAE,6BAA6B,EAAoB,MAAM,WAAW,CAAC;AA6BrF,cAAM,oBAAqB,SAAQ,6BAA6B;IAC9D,OAAO,CAAC,UAAU,CAAS;IAE3B,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAcpC,SAAS,CAAC,eAAe,IAAI,OAAO;cAIpB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;CAyChG;AAED,qBAAa,MAAO,YAAW,OAAO;IACpC,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,WAAW,uCAAwC;IAC5D,QAAQ,CAAC,QAAQ,4CAAwB;IAEzC,QAAQ,CAAC,4BAA4B,2CAA4C;IAEjF,UAAU,IAAI,oBAAoB;IAIlC,mBAAmB,CAAC,cAAc,EAAE,cAAc,GAAG,mBAAmB;CAuBzE;AAED,eAAO,MAAM,MAAM,QAAe,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub service implementation.
|
|
3
|
+
*/
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { ApiCredentialStatus, AuthorizationBearer } from '../apiCredentials.js';
|
|
6
|
+
import { runCaptured } from '../curl.js';
|
|
7
|
+
import { typeLikeHuman } from '../playwrightUtils.js';
|
|
8
|
+
import { BrowserFollowupServiceSession, LoginFailedError } from './base.js';
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 8000;
|
|
10
|
+
// URL for creating a new personal access token (also used as login URL to trigger sudo)
|
|
11
|
+
const GITHUB_NEW_TOKEN_URL = 'https://github.com/settings/tokens/new';
|
|
12
|
+
// GitHub personal access token scopes to enable
|
|
13
|
+
const GITHUB_TOKEN_SCOPES = [
|
|
14
|
+
'repo',
|
|
15
|
+
'workflow',
|
|
16
|
+
'write:packages',
|
|
17
|
+
'delete:packages',
|
|
18
|
+
'gist',
|
|
19
|
+
'notifications',
|
|
20
|
+
'admin:org',
|
|
21
|
+
'admin:repo_hook',
|
|
22
|
+
'admin:org_hook',
|
|
23
|
+
'user',
|
|
24
|
+
'delete_repo',
|
|
25
|
+
'write:discussion',
|
|
26
|
+
'admin:enterprise',
|
|
27
|
+
'read:audit_log',
|
|
28
|
+
'codespace',
|
|
29
|
+
'copilot',
|
|
30
|
+
'write:network_configurations',
|
|
31
|
+
'project',
|
|
32
|
+
];
|
|
33
|
+
class GithubServiceSession extends BrowserFollowupServiceSession {
|
|
34
|
+
isLoggedIn = false;
|
|
35
|
+
onResponse(response) {
|
|
36
|
+
if (this.isLoggedIn) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const request = response.request();
|
|
40
|
+
// Detect login (and github's sudo) by seeing if github allows us to access the new token page.
|
|
41
|
+
if (request.url() === GITHUB_NEW_TOKEN_URL) {
|
|
42
|
+
if (response.status() === 200) {
|
|
43
|
+
this.isLoggedIn = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
isLoginComplete() {
|
|
48
|
+
return this.isLoggedIn;
|
|
49
|
+
}
|
|
50
|
+
async performBrowserFollowup(context) {
|
|
51
|
+
const page = context.pages()[0];
|
|
52
|
+
if (!page) {
|
|
53
|
+
throw new LoginFailedError('No page available in browser context.');
|
|
54
|
+
}
|
|
55
|
+
await page.goto(GITHUB_NEW_TOKEN_URL);
|
|
56
|
+
// Add a note for the token
|
|
57
|
+
const noteInput = page.locator('//*[@id="oauth_access_description"]');
|
|
58
|
+
await noteInput.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
59
|
+
await typeLikeHuman(page, noteInput, `Latchkey-${randomUUID().slice(0, 8)}`);
|
|
60
|
+
// Enable all necessary scopes
|
|
61
|
+
for (const scope of GITHUB_TOKEN_SCOPES) {
|
|
62
|
+
const checkbox = page.locator(`input[name="oauth_access[scopes][]"][value="${scope}"]`);
|
|
63
|
+
if (await checkbox.isVisible()) {
|
|
64
|
+
await checkbox.check();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Click the Generate Token button
|
|
68
|
+
const generateButton = page.locator('button[type="submit"].btn-primary:has-text("Generate token")');
|
|
69
|
+
await generateButton.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
70
|
+
await generateButton.click();
|
|
71
|
+
// Wait for the page to load and retrieve the generated token
|
|
72
|
+
const tokenElement = page.locator('//*[@id="new-oauth-token"]');
|
|
73
|
+
await tokenElement.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
|
|
74
|
+
const token = await tokenElement.textContent();
|
|
75
|
+
if (token === null || token === '') {
|
|
76
|
+
throw new LoginFailedError('Failed to extract token from GitHub.');
|
|
77
|
+
}
|
|
78
|
+
await page.close();
|
|
79
|
+
return new AuthorizationBearer(token);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export class Github {
|
|
83
|
+
name = 'github';
|
|
84
|
+
baseApiUrls = ['https://api.github.com/'];
|
|
85
|
+
loginUrl = GITHUB_NEW_TOKEN_URL;
|
|
86
|
+
credentialCheckCurlArguments = ['https://api.github.com/user'];
|
|
87
|
+
getSession() {
|
|
88
|
+
return new GithubServiceSession(this);
|
|
89
|
+
}
|
|
90
|
+
checkApiCredentials(apiCredentials) {
|
|
91
|
+
if (!(apiCredentials instanceof AuthorizationBearer)) {
|
|
92
|
+
return ApiCredentialStatus.Invalid;
|
|
93
|
+
}
|
|
94
|
+
const result = runCaptured([
|
|
95
|
+
'-s',
|
|
96
|
+
'-o',
|
|
97
|
+
'/dev/null',
|
|
98
|
+
'-w',
|
|
99
|
+
'%{http_code}',
|
|
100
|
+
...apiCredentials.asCurlArguments(),
|
|
101
|
+
...this.credentialCheckCurlArguments,
|
|
102
|
+
], 10);
|
|
103
|
+
if (result.stdout === '200') {
|
|
104
|
+
return ApiCredentialStatus.Valid;
|
|
105
|
+
}
|
|
106
|
+
return ApiCredentialStatus.Invalid;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export const GITHUB = new Github();
|
|
110
|
+
//# sourceMappingURL=github.js.map
|