latchkey 3.2.2 → 3.3.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 (48) hide show
  1. package/README.md +2 -1
  2. package/dist/src/cliCommands.d.ts.map +1 -1
  3. package/dist/src/cliCommands.js +91 -7
  4. package/dist/src/cliCommands.js.map +1 -1
  5. package/dist/src/configDataStore.d.ts +18 -0
  6. package/dist/src/configDataStore.d.ts.map +1 -1
  7. package/dist/src/configDataStore.js +10 -0
  8. package/dist/src/configDataStore.js.map +1 -1
  9. package/dist/src/serviceRegistry.d.ts.map +1 -1
  10. package/dist/src/serviceRegistry.js +25 -2
  11. package/dist/src/serviceRegistry.js.map +1 -1
  12. package/dist/src/services/core/base.d.ts +25 -3
  13. package/dist/src/services/core/base.d.ts.map +1 -1
  14. package/dist/src/services/core/base.js +32 -10
  15. package/dist/src/services/core/base.js.map +1 -1
  16. package/dist/src/services/core/loginFlows/base.d.ts +75 -0
  17. package/dist/src/services/core/loginFlows/base.d.ts.map +1 -0
  18. package/dist/src/services/core/loginFlows/base.js +47 -0
  19. package/dist/src/services/core/loginFlows/base.js.map +1 -0
  20. package/dist/src/services/core/loginFlows/cookieCapture.d.ts +55 -0
  21. package/dist/src/services/core/loginFlows/cookieCapture.d.ts.map +1 -0
  22. package/dist/src/services/core/loginFlows/cookieCapture.js +244 -0
  23. package/dist/src/services/core/loginFlows/cookieCapture.js.map +1 -0
  24. package/dist/src/services/core/loginFlows/registry.d.ts +30 -0
  25. package/dist/src/services/core/loginFlows/registry.d.ts.map +1 -0
  26. package/dist/src/services/core/loginFlows/registry.js +56 -0
  27. package/dist/src/services/core/loginFlows/registry.js.map +1 -0
  28. package/dist/src/services/core/registered.d.ts +38 -2
  29. package/dist/src/services/core/registered.d.ts.map +1 -1
  30. package/dist/src/services/core/registered.js +37 -2
  31. package/dist/src/services/core/registered.js.map +1 -1
  32. package/dist/src/services/index.d.ts +3 -0
  33. package/dist/src/services/index.d.ts.map +1 -1
  34. package/dist/src/services/index.js +3 -0
  35. package/dist/src/services/index.js.map +1 -1
  36. package/dist/src/version.d.ts +1 -1
  37. package/dist/src/version.js +1 -1
  38. package/dist/tests/cli.test.js +178 -2
  39. package/dist/tests/cli.test.js.map +1 -1
  40. package/dist/tests/cookieCapture.test.d.ts +10 -0
  41. package/dist/tests/cookieCapture.test.d.ts.map +1 -0
  42. package/dist/tests/cookieCapture.test.js +221 -0
  43. package/dist/tests/cookieCapture.test.js.map +1 -0
  44. package/dist/tests/serviceRegistry.test.js +29 -8
  45. package/dist/tests/serviceRegistry.test.js.map +1 -1
  46. package/dist/tests/servicesAgainstRecordings.test.js +5 -9
  47. package/dist/tests/servicesAgainstRecordings.test.js.map +1 -1
  48. package/package.json +1 -1
@@ -3,10 +3,46 @@
3
3
  * with a custom name and base API URL. Used for self-hosted instances.
4
4
  *
5
5
  * When no family service is provided, the registered service acts as a
6
- * generic service that only supports `latchkey auth set` for credentials.
6
+ * generic service that supports `latchkey auth set` for credentials, plus —
7
+ * if a login URL and a login flow were given — a browser login of its own.
7
8
  */
8
9
  import { ApiCredentialStatus, type ApiCredentials } from '../../apiCredentials/base.js';
9
10
  import { Service, type ServiceSession } from './base.js';
11
+ import type { LoginFlow } from './loginFlows/base.js';
12
+ /**
13
+ * Where a registered service gets its browser login, if anywhere. The two
14
+ * sources are mutually exclusive — a family service already brings its own
15
+ * login and credential shape — so they are alternatives rather than fields
16
+ * that happen not to be combined. Omitting the options entirely leaves the
17
+ * service with `latchkey auth set` only.
18
+ *
19
+ * The `never` markers are what make the exclusion stick: a plain union of the
20
+ * two shapes would still accept an object carrying both, because excess
21
+ * property checking against a union permits any property known to some member.
22
+ */
23
+ export type RegisteredServiceOptions = {
24
+ /** Built-in service to use as a template, for self-hosted instances. */
25
+ readonly familyService: Service;
26
+ /** Page opened by `latchkey auth browser`, for families that support it. */
27
+ readonly loginUrl?: string;
28
+ readonly loginFlow?: never;
29
+ } | {
30
+ /** Generic browser login, already configured with its parameters. */
31
+ readonly loginFlow: LoginFlow;
32
+ /** Page the flow starts from. A flow has nowhere to begin without one. */
33
+ readonly loginUrl: string;
34
+ readonly familyService?: never;
35
+ };
36
+ /**
37
+ * Build options from values that are each independently optional, as they come
38
+ * out of the CLI and out of config.json.
39
+ *
40
+ * This is the one place where a family service and a login flow can meet, so it
41
+ * is also where their precedence is decided: the family wins, since its login
42
+ * and credential shape are what the rest of the service is built on. Callers
43
+ * that construct options directly are held to the exclusion by the type.
44
+ */
45
+ export declare function buildRegisteredServiceOptions(familyService: Service | undefined, loginUrl: string | undefined, loginFlow: LoginFlow | undefined): RegisteredServiceOptions | undefined;
10
46
  export declare class RegisteredService extends Service {
11
47
  readonly name: string;
12
48
  readonly displayName: string;
@@ -15,7 +51,7 @@ export declare class RegisteredService extends Service {
15
51
  readonly info: string;
16
52
  readonly credentialCheckCurlArguments: readonly string[];
17
53
  private readonly familyService;
18
- constructor(name: string, baseApiUrl: string, familyService?: Service, loginUrl?: string);
54
+ constructor(name: string, baseApiUrl: string, options?: RegisteredServiceOptions);
19
55
  getSession?(appNamePrefix: string): ServiceSession;
20
56
  checkApiCredentials(): Promise<ApiCredentialStatus>;
21
57
  getAccount(): Promise<string | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"registered.d.ts","sourceRoot":"","sources":["../../../../src/services/core/registered.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEzD,qBAAa,iBAAkB,SAAQ,OAAO;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,4BAA4B,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;gBAExC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM;IAqB/E,UAAU,CAAC,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc;IAElD,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAM5D,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIpC,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAOzC,oBAAoB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc;CAM7E"}
1
+ {"version":3,"file":"registered.d.ts","sourceRoot":"","sources":["../../../../src/services/core/registered.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,wBAAwB,GAChC;IACE,wEAAwE;IACxE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC;CAC5B,GACD;IACE,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC,CAAC;AAEN;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,OAAO,GAAG,SAAS,EAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,SAAS,GAAG,SAAS,GAC/B,wBAAwB,GAAG,SAAS,CAQtC;AAED,qBAAa,iBAAkB,SAAQ,OAAO;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,4BAA4B,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;gBAExC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,wBAAwB;IAsCvE,UAAU,CAAC,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc;IAElD,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAM5D,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIpC,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAOzC,oBAAoB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc;CAM7E"}
@@ -3,10 +3,29 @@
3
3
  * with a custom name and base API URL. Used for self-hosted instances.
4
4
  *
5
5
  * When no family service is provided, the registered service acts as a
6
- * generic service that only supports `latchkey auth set` for credentials.
6
+ * generic service that supports `latchkey auth set` for credentials, plus —
7
+ * if a login URL and a login flow were given — a browser login of its own.
7
8
  */
8
9
  import { ApiCredentialStatus } from '../../apiCredentials/base.js';
9
10
  import { Service } from './base.js';
11
+ /**
12
+ * Build options from values that are each independently optional, as they come
13
+ * out of the CLI and out of config.json.
14
+ *
15
+ * This is the one place where a family service and a login flow can meet, so it
16
+ * is also where their precedence is decided: the family wins, since its login
17
+ * and credential shape are what the rest of the service is built on. Callers
18
+ * that construct options directly are held to the exclusion by the type.
19
+ */
20
+ export function buildRegisteredServiceOptions(familyService, loginUrl, loginFlow) {
21
+ if (familyService !== undefined) {
22
+ return { familyService, loginUrl };
23
+ }
24
+ if (loginFlow !== undefined && loginUrl !== undefined) {
25
+ return { loginFlow, loginUrl };
26
+ }
27
+ return undefined;
28
+ }
10
29
  export class RegisteredService extends Service {
11
30
  name;
12
31
  displayName;
@@ -15,8 +34,16 @@ export class RegisteredService extends Service {
15
34
  info;
16
35
  credentialCheckCurlArguments;
17
36
  familyService;
18
- constructor(name, baseApiUrl, familyService, loginUrl) {
37
+ constructor(name, baseApiUrl, options) {
19
38
  super();
39
+ const familyService = options?.familyService;
40
+ const loginUrl = options?.loginUrl;
41
+ // The options type rules out a flow without a page to open, and a flow
42
+ // alongside a family service, for TypeScript callers. This repeats the
43
+ // checks because latchkey is also consumed as a library from JavaScript.
44
+ const usableLoginFlow = options?.loginFlow !== undefined && loginUrl !== undefined && familyService === undefined
45
+ ? { flow: options.loginFlow, loginUrl }
46
+ : undefined;
20
47
  this.name = name;
21
48
  this.displayName = name;
22
49
  this.baseApiUrls = [baseApiUrl];
@@ -26,6 +53,11 @@ export class RegisteredService extends Service {
26
53
  if (familyService !== undefined) {
27
54
  this.info = `Self-hosted ${familyService.displayName} instance. ${familyService.info}`;
28
55
  }
56
+ else if (usableLoginFlow !== undefined) {
57
+ this.info =
58
+ `Generic service. ${usableLoginFlow.flow.describe(usableLoginFlow.loginUrl)} ` +
59
+ 'Alternatively, use `latchkey auth set` to supply credentials as curl arguments.';
60
+ }
29
61
  else {
30
62
  this.info =
31
63
  'Generic service. Use `latchkey auth set` to supply credentials as curl arguments.';
@@ -33,6 +65,9 @@ export class RegisteredService extends Service {
33
65
  if (loginUrl !== undefined && familyService?.getSession !== undefined) {
34
66
  this.getSession = (appNamePrefix) => familyService.getSession(appNamePrefix);
35
67
  }
68
+ else if (usableLoginFlow !== undefined) {
69
+ this.getSession = (appNamePrefix) => usableLoginFlow.flow.createSession(this, appNamePrefix);
70
+ }
36
71
  }
37
72
  checkApiCredentials() {
38
73
  return Promise.resolve(ApiCredentialStatus.Unknown);
@@ -1 +1 @@
1
- {"version":3,"file":"registered.js","sourceRoot":"","sources":["../../../../src/services/core/registered.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAuB,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,OAAO,EAAuB,MAAM,WAAW,CAAC;AAEzD,MAAM,OAAO,iBAAkB,SAAQ,OAAO;IACnC,IAAI,CAAS;IACb,WAAW,CAAS;IACpB,WAAW,CAAoB;IAC/B,QAAQ,CAAS;IACjB,IAAI,CAAS;IACb,4BAA4B,CAAoB;IAExC,aAAa,CAAsB;IAEpD,YAAY,IAAY,EAAE,UAAkB,EAAE,aAAuB,EAAE,QAAiB;QACtF,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,CAAC,UAAU,CAAC,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,4BAA4B,GAAG,EAAE,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QAEnC,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,GAAG,eAAe,aAAa,CAAC,WAAW,cAAc,aAAa,CAAC,IAAI,EAAE,CAAC;QACzF,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI;gBACP,mFAAmF,CAAC;QACxF,CAAC;QAED,IAAI,QAAQ,KAAK,SAAS,IAAI,aAAa,EAAE,UAAU,KAAK,SAAS,EAAE,CAAC;YACtE,IAAI,CAAC,UAAU,GAAG,CAAC,aAAqB,EAAE,EAAE,CAAC,aAAa,CAAC,UAAW,CAAC,aAAa,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAIQ,mBAAmB;QAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,wEAAwE;IACxE,2DAA2D;IAC3D,UAAU;QACR,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,qBAAqB,CAAC,WAAmB;QACvC,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,qBAAqB,WAAW,qCAAqC,CAAC;IAC/E,CAAC;IAEQ,oBAAoB,CAAC,UAA6B;QACzD,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,KAAK,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC;CACF"}
1
+ {"version":3,"file":"registered.js","sourceRoot":"","sources":["../../../../src/services/core/registered.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,mBAAmB,EAAuB,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,OAAO,EAAuB,MAAM,WAAW,CAAC;AA8BzD;;;;;;;;GAQG;AACH,MAAM,UAAU,6BAA6B,CAC3C,aAAkC,EAClC,QAA4B,EAC5B,SAAgC;IAEhC,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACtD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IACjC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,OAAO,iBAAkB,SAAQ,OAAO;IACnC,IAAI,CAAS;IACb,WAAW,CAAS;IACpB,WAAW,CAAoB;IAC/B,QAAQ,CAAS;IACjB,IAAI,CAAS;IACb,4BAA4B,CAAoB;IAExC,aAAa,CAAsB;IAEpD,YAAY,IAAY,EAAE,UAAkB,EAAE,OAAkC;QAC9E,KAAK,EAAE,CAAC;QACR,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,CAAC;QAC7C,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,CAAC;QACnC,uEAAuE;QACvE,uEAAuE;QACvE,yEAAyE;QACzE,MAAM,eAAe,GACnB,OAAO,EAAE,SAAS,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,IAAI,aAAa,KAAK,SAAS;YACvF,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE;YACvC,CAAC,CAAC,SAAS,CAAC;QAEhB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,CAAC,UAAU,CAAC,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,4BAA4B,GAAG,EAAE,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QAEnC,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,GAAG,eAAe,aAAa,CAAC,WAAW,cAAc,aAAa,CAAC,IAAI,EAAE,CAAC;QACzF,CAAC;aAAM,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI;gBACP,oBAAoB,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,QAAQ,CAAC,GAAG;oBAC9E,iFAAiF,CAAC;QACtF,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI;gBACP,mFAAmF,CAAC;QACxF,CAAC;QAED,IAAI,QAAQ,KAAK,SAAS,IAAI,aAAa,EAAE,UAAU,KAAK,SAAS,EAAE,CAAC;YACtE,IAAI,CAAC,UAAU,GAAG,CAAC,aAAqB,EAAE,EAAE,CAAC,aAAa,CAAC,UAAW,CAAC,aAAa,CAAC,CAAC;QACxF,CAAC;aAAM,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,UAAU,GAAG,CAAC,aAAqB,EAAE,EAAE,CAC1C,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAIQ,mBAAmB;QAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,wEAAwE;IACxE,2DAA2D;IAC3D,UAAU;QACR,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,qBAAqB,CAAC,WAAmB;QACvC,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,qBAAqB,WAAW,qCAAqC,CAAC;IAC/E,CAAC;IAEQ,oBAAoB,CAAC,UAA6B;QACzD,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,KAAK,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC;CACF"}
@@ -4,6 +4,9 @@
4
4
  export { Service, ServiceSession, SimpleServiceSession, BrowserFollowupServiceSession, FollowupWork, buildFollowupSpinnerDetails, type LoginResult, } from './core/base.js';
5
5
  export { LoginCancelledError, LoginFailedError, NoCurlCredentialsNotSupportedError, PrepareNotSupportedError, PrepareInputInvalidError, } from './core/base.js';
6
6
  export { RegisteredService } from './core/registered.js';
7
+ export { LOGIN_FLOWS, UnknownLoginFlowError, formatLoginFlowsHelp, resolveLoginFlow, } from './core/loginFlows/registry.js';
8
+ export { LoginFlowParamsInvalidError, parseLoginFlowParams, type LoginFlow, type LoginFlowClass, } from './core/loginFlows/base.js';
9
+ export { CookieCaptureLoginFlow } from './core/loginFlows/cookieCapture.js';
7
10
  export { Slack, SLACK } from './slack.js';
8
11
  export { Discord, DISCORD } from './discord.js';
9
12
  export { Github, GITHUB } from './github.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,OAAO,EACP,cAAc,EACd,oBAAoB,EACpB,6BAA6B,EAC7B,YAAY,EACZ,2BAA2B,EAC3B,KAAK,WAAW,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kCAAkC,EAClC,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/index.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,9 @@
4
4
  export { Service, ServiceSession, SimpleServiceSession, BrowserFollowupServiceSession, FollowupWork, buildFollowupSpinnerDetails, } from './core/base.js';
5
5
  export { LoginCancelledError, LoginFailedError, NoCurlCredentialsNotSupportedError, PrepareNotSupportedError, PrepareInputInvalidError, } from './core/base.js';
6
6
  export { RegisteredService } from './core/registered.js';
7
+ export { LOGIN_FLOWS, UnknownLoginFlowError, formatLoginFlowsHelp, resolveLoginFlow, } from './core/loginFlows/registry.js';
8
+ export { LoginFlowParamsInvalidError, parseLoginFlowParams, } from './core/loginFlows/base.js';
9
+ export { CookieCaptureLoginFlow } from './core/loginFlows/cookieCapture.js';
7
10
  export { Slack, SLACK } from './slack.js';
8
11
  export { Discord, DISCORD } from './discord.js';
9
12
  export { Github, GITHUB } from './github.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,OAAO,EACP,cAAc,EACd,oBAAoB,EACpB,6BAA6B,EAC7B,YAAY,EACZ,2BAA2B,GAE5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kCAAkC,EAClC,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/index.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.2.2";
1
+ export declare const VERSION = "3.3.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // Auto-generated from package.json by scripts/generateVersion.js.
2
2
  // Do not edit by hand; run `node scripts/generateVersion.js` to refresh.
3
- export const VERSION = "3.2.2";
3
+ export const VERSION = "3.3.0";
4
4
  //# sourceMappingURL=version.js.map
@@ -1742,7 +1742,7 @@ describe('CLI commands with dependency injection', () => {
1742
1742
  const entries = loadRegisteredServices(deps.config.configPath);
1743
1743
  expect(entries.get('my-github')?.loginUrl).toBe('https://github.mycompany.com/login');
1744
1744
  });
1745
- it('should reject --login-url without --service-family', async () => {
1745
+ it('should reject --login-url without --service-family or --login-flow', async () => {
1746
1746
  const deps = createMockDependencies({
1747
1747
  registry: new ServiceRegistry([]),
1748
1748
  });
@@ -1756,7 +1756,154 @@ describe('CLI commands with dependency injection', () => {
1756
1756
  'https://example.com/login',
1757
1757
  ], deps);
1758
1758
  expect(exitCode).toBe(1);
1759
- expect(errorLogs[0]).toContain('--login-url requires a --service-family');
1759
+ expect(errorLogs[0]).toContain('--login-url requires either a --service-family');
1760
+ });
1761
+ it('should register a service with the cookie-capture login flow', async () => {
1762
+ const deps = createMockDependencies({
1763
+ registry: new ServiceRegistry([]),
1764
+ });
1765
+ await runCommand([
1766
+ 'services',
1767
+ 'register',
1768
+ 'my-service',
1769
+ '--base-api-url',
1770
+ 'https://example.com/api/',
1771
+ '--login-url',
1772
+ 'https://example.com/login',
1773
+ '--login-flow',
1774
+ 'cookie-capture',
1775
+ '--login-flow-params',
1776
+ '{"cookieKeys": ["sessionid", "csrftoken"]}',
1777
+ ], deps);
1778
+ expect(exitCode).toBeNull();
1779
+ expect(logs).toContain("Service 'my-service' registered.");
1780
+ expect(loadRegisteredServices(deps.config.configPath).get('my-service')).toEqual({
1781
+ baseApiUrl: 'https://example.com/api/',
1782
+ loginUrl: 'https://example.com/login',
1783
+ loginFlow: {
1784
+ name: 'cookie-capture',
1785
+ params: { cookieKeys: ['sessionid', 'csrftoken'] },
1786
+ },
1787
+ });
1788
+ logs = [];
1789
+ await runCommand(['services', 'info', 'my-service'], deps);
1790
+ const info = JSON.parse(logs[0] ?? '');
1791
+ expect(info.authOptions).toEqual(['browser', 'set']);
1792
+ });
1793
+ it('should reject an unknown login flow and list the available ones', async () => {
1794
+ const deps = createMockDependencies({
1795
+ registry: new ServiceRegistry([]),
1796
+ });
1797
+ await runCommand([
1798
+ 'services',
1799
+ 'register',
1800
+ 'my-service',
1801
+ '--base-api-url',
1802
+ 'https://example.com/api/',
1803
+ '--login-url',
1804
+ 'https://example.com/login',
1805
+ '--login-flow',
1806
+ 'nonexistent',
1807
+ ], deps);
1808
+ expect(exitCode).toBe(1);
1809
+ expect(errorLogs[0]).toContain("Unknown login flow 'nonexistent'");
1810
+ expect(errorLogs.join('\n')).toContain('cookie-capture');
1811
+ });
1812
+ it('should reject login flow parameters that are not valid JSON', async () => {
1813
+ const deps = createMockDependencies({
1814
+ registry: new ServiceRegistry([]),
1815
+ });
1816
+ await runCommand([
1817
+ 'services',
1818
+ 'register',
1819
+ 'my-service',
1820
+ '--base-api-url',
1821
+ 'https://example.com/api/',
1822
+ '--login-url',
1823
+ 'https://example.com/login',
1824
+ '--login-flow',
1825
+ 'cookie-capture',
1826
+ '--login-flow-params',
1827
+ '{not json}',
1828
+ ], deps);
1829
+ expect(exitCode).toBe(1);
1830
+ expect(errorLogs[0]).toContain('--login-flow-params must be valid JSON');
1831
+ });
1832
+ it('should reject login flow parameters that do not match the flow schema', async () => {
1833
+ const deps = createMockDependencies({
1834
+ registry: new ServiceRegistry([]),
1835
+ });
1836
+ await runCommand([
1837
+ 'services',
1838
+ 'register',
1839
+ 'my-service',
1840
+ '--base-api-url',
1841
+ 'https://example.com/api/',
1842
+ '--login-url',
1843
+ 'https://example.com/login',
1844
+ '--login-flow',
1845
+ 'cookie-capture',
1846
+ '--login-flow-params',
1847
+ '{"cookieKeys": []}',
1848
+ ], deps);
1849
+ expect(exitCode).toBe(1);
1850
+ expect(errorLogs[0]).toContain("Invalid parameters for login flow 'cookie-capture'");
1851
+ });
1852
+ it('should reject --login-flow-params without --login-flow', async () => {
1853
+ const deps = createMockDependencies({
1854
+ registry: new ServiceRegistry([]),
1855
+ });
1856
+ await runCommand([
1857
+ 'services',
1858
+ 'register',
1859
+ 'my-service',
1860
+ '--base-api-url',
1861
+ 'https://example.com/api/',
1862
+ '--login-url',
1863
+ 'https://example.com/login',
1864
+ '--login-flow-params',
1865
+ '{"cookieKeys": ["sessionid"]}',
1866
+ ], deps);
1867
+ expect(exitCode).toBe(1);
1868
+ expect(errorLogs[0]).toContain('--login-flow-params requires --login-flow');
1869
+ });
1870
+ it('should reject --login-flow without --login-url', async () => {
1871
+ const deps = createMockDependencies({
1872
+ registry: new ServiceRegistry([]),
1873
+ });
1874
+ await runCommand([
1875
+ 'services',
1876
+ 'register',
1877
+ 'my-service',
1878
+ '--base-api-url',
1879
+ 'https://example.com/api/',
1880
+ '--login-flow',
1881
+ 'cookie-capture',
1882
+ '--login-flow-params',
1883
+ '{"cookieKeys": ["sessionid"]}',
1884
+ ], deps);
1885
+ expect(exitCode).toBe(1);
1886
+ expect(errorLogs[0]).toContain('--login-flow requires --login-url');
1887
+ });
1888
+ it('should reject --login-flow combined with --service-family', async () => {
1889
+ const deps = createMockDependencies({
1890
+ registry: new ServiceRegistry([GITLAB]),
1891
+ });
1892
+ await runCommand([
1893
+ 'services',
1894
+ 'register',
1895
+ 'my-gitlab',
1896
+ '--base-api-url',
1897
+ 'https://gitlab.mycompany.com/api/',
1898
+ '--service-family',
1899
+ 'gitlab',
1900
+ '--login-url',
1901
+ 'https://gitlab.mycompany.com/users/sign_in',
1902
+ '--login-flow',
1903
+ 'cookie-capture',
1904
+ ], deps);
1905
+ expect(exitCode).toBe(1);
1906
+ expect(errorLogs[0]).toContain('--login-flow cannot be combined with --service-family');
1760
1907
  });
1761
1908
  it('should reject --login-url when service family does not support browser login', async () => {
1762
1909
  const deps = createMockDependencies({
@@ -2413,6 +2560,35 @@ describe('registeredServiceStore', () => {
2413
2560
  expect(service.getSession).toBeUndefined(); // eslint-disable-line @typescript-eslint/unbound-method
2414
2561
  expect(service.loginUrl).toBe('');
2415
2562
  });
2563
+ it('should load registered service with a login flow into registry', () => {
2564
+ const configPath = join(tempDir, 'config.json');
2565
+ saveRegisteredService(configPath, 'my-api', {
2566
+ baseApiUrl: 'https://api.example.com/',
2567
+ loginUrl: 'https://example.com/login',
2568
+ loginFlow: { name: 'cookie-capture', params: { cookieKeys: ['session_id'] } },
2569
+ });
2570
+ const registry = new ServiceRegistry([GITLAB]);
2571
+ loadRegisteredServicesIntoServiceRegistry(configPath, registry);
2572
+ const service = registry.getByName('my-api');
2573
+ expect(service).not.toBeNull();
2574
+ expect(service.getSession).toBeDefined(); // eslint-disable-line @typescript-eslint/unbound-method
2575
+ expect(service.loginUrl).toBe('https://example.com/login');
2576
+ });
2577
+ it('should keep a service whose stored login flow no longer resolves', () => {
2578
+ const configPath = join(tempDir, 'config.json');
2579
+ saveRegisteredService(configPath, 'my-api', {
2580
+ baseApiUrl: 'https://api.example.com/',
2581
+ loginUrl: 'https://example.com/login',
2582
+ // Written by a newer Latchkey, or hand-edited: the service stays usable
2583
+ // with `auth set`, it just loses the browser login.
2584
+ loginFlow: { name: 'flow-from-the-future', params: { anything: true } },
2585
+ });
2586
+ const registry = new ServiceRegistry([GITLAB]);
2587
+ loadRegisteredServicesIntoServiceRegistry(configPath, registry);
2588
+ const service = registry.getByName('my-api');
2589
+ expect(service).not.toBeNull();
2590
+ expect(service.getSession).toBeUndefined(); // eslint-disable-line @typescript-eslint/unbound-method
2591
+ });
2416
2592
  it('should skip registered services with unknown family', () => {
2417
2593
  const configPath = join(tempDir, 'config.json');
2418
2594
  saveRegisteredService(configPath, 'my-unknown', {