@knpkv/jcf-web 0.2.0 → 0.4.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/dist/main.js CHANGED
@@ -5,25 +5,27 @@
5
5
  * @module
6
6
  */
7
7
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
8
+ import { loopbackOrigin } from "@knpkv/browser-pairing/owner-session";
8
9
  import { Deferred, Effect, Fiber, Layer, Option } from "effect";
9
10
  import * as Stdio from "effect/Stdio";
10
11
  import * as Stream from "effect/Stream";
11
- import { makeOwnerSessionSecrets, ownerSessionOrigin, ownerSessionUrl } from "./server/OwnerSession.js";
12
+ import { makeOwnerSession } from "./server/OwnerSession.js";
12
13
  import { makeServer, Port, PublicOrigin } from "./server/Server.js";
13
14
  const run = Effect.gen(function* () {
14
15
  const stdio = yield* Stdio.Stdio;
15
16
  const port = yield* Port.pipe(Effect.orDie);
16
17
  const configuredOrigin = yield* PublicOrigin.pipe(Effect.orDie);
17
- const authorityOrigin = ownerSessionOrigin("127.0.0.1", port);
18
+ const authorityOrigin = loopbackOrigin("127.0.0.1", port);
18
19
  // One origin decision: the URL that gets printed is the same origin every request is checked
19
20
  // against, so a dev proxy in front of the server cannot be advertised and then rejected.
20
- const security = yield* makeOwnerSessionSecrets(authorityOrigin, Option.getOrUndefined(configuredOrigin)).pipe(Effect.orDie);
21
+ const security = yield* makeOwnerSession(authorityOrigin, Option.getOrUndefined(configuredOrigin)).pipe(Effect.orDie);
21
22
  const ready = yield* Deferred.make();
22
23
  const server = yield* Layer.launch(makeServer({ port, ready, security })).pipe(Effect.forkChild({ startImmediately: true }));
23
- yield* Effect.raceFirst(Deferred.await(ready), Fiber.join(server));
24
+ // The server only ends this race by failing; until it is listening there is no link to print.
25
+ const url = yield* Effect.raceFirst(Deferred.await(ready), Fiber.join(server).pipe(Effect.andThen(Effect.never)));
24
26
  // On stdout and nowhere else: this line is the credential. It is not logged, so it cannot end up
25
27
  // in a log file that outlives the process that minted it.
26
- yield* Stream.make(`jcf week view: ${ownerSessionUrl(security.browserOrigin, security)}\n`).pipe(Stream.run(stdio.stdout()));
28
+ yield* Stream.make(`jcf week view: ${url}\n`).pipe(Stream.run(stdio.stdout()));
27
29
  return yield* Fiber.join(server);
28
30
  });
29
31
  // Executable entry point: the host platform is provided once for this process.
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/D,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACvG,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEnE,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAA;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC/D,MAAM,eAAe,GAAG,kBAAkB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;IAE7D,6FAA6F;IAC7F,yFAAyF;IACzF,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,uBAAuB,CAC7C,eAAe,EACf,MAAM,CAAC,cAAc,CAAC,gBAAgB,CAAC,CACxC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAEpB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ,CAAA;IAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAC5E,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAA;IACD,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;IAClE,iGAAiG;IACjG,0DAA0D;IAC1D,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,eAAe,CAAC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAC9F,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAC3B,CAAA;IACD,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AAClC,CAAC,CAAC,CAAA;AAEF,+EAA+E;AAC/E,wDAAwD;AACxD,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAA;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/D,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEnE,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAA;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC/D,MAAM,eAAe,GAAG,cAAc,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;IAEzD,6FAA6F;IAC7F,yFAAyF;IACzF,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,gBAAgB,CACtC,eAAe,EACf,MAAM,CAAC,cAAc,CAAC,gBAAgB,CAAC,CACxC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAEpB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAU,CAAA;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAC5E,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAA;IACD,8FAA8F;IAC9F,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACjH,iGAAiG;IACjG,0DAA0D;IAC1D,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC,IAAI,CAChD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAC3B,CAAA;IACD,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AAClC,CAAC,CAAC,CAAA;AAEF,+EAA+E;AAC/E,wDAAwD;AACxD,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA"}
@@ -468,17 +468,23 @@ declare const JcfWebApi_base: HttpApi.HttpApi<"JcfWebApi", HttpApiGroup.HttpApiG
468
468
  readonly endMs: Schema.Number;
469
469
  readonly description: Schema.NullOr<Schema.String>;
470
470
  }>;
471
- }>>, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError | typeof ProposalRejectedError>, OwnerSessionAuth, never>, false> | HttpApiGroup.HttpApiGroup<"rows", HttpApiEndpoint.HttpApiEndpoint<"confirm", "POST", "/api/rows/confirm", never, never, Schema.toCodecJson<Schema.Struct<{
472
- readonly blocks: Schema.optional<Schema.$Array<Schema.Number>>;
473
- readonly note: Schema.optional<Schema.String>;
471
+ }>>, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError | typeof ProposalRejectedError>, OwnerSessionAuth, never>, false> | HttpApiGroup.HttpApiGroup<"rows", HttpApiEndpoint.HttpApiEndpoint<"describe", "POST", "/api/rows/describe", never, never, Schema.toCodecJson<Schema.Struct<{
472
+ readonly planId: Schema.String;
473
+ readonly rowId: Schema.String;
474
+ }>>, never, Schema.toCodecJson<Schema.Struct<{
474
475
  readonly planId: Schema.String;
475
476
  readonly rowId: Schema.String;
477
+ readonly note: Schema.NullOr<Schema.String>;
478
+ }>>, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"manual", "POST", "/api/rows/manual", never, never, Schema.toCodecJson<Schema.Struct<{
479
+ readonly day: Schema.String;
480
+ readonly note: Schema.optional<Schema.String>;
481
+ readonly seconds: Schema.Number;
476
482
  readonly targets: Schema.optional<Schema.Struct<{
477
483
  readonly clockify: Schema.Boolean;
478
484
  readonly jira: Schema.Boolean;
479
485
  }>>;
480
- readonly seconds: Schema.optional<Schema.Number>;
481
- readonly ticketKey: Schema.optional<Schema.String>;
486
+ readonly startClock: Schema.optional<Schema.String>;
487
+ readonly ticketKey: Schema.String;
482
488
  }>>, never, Schema.toCodecJson<Schema.Struct<{
483
489
  readonly clockify: Schema.Union<readonly [Schema.TaggedStruct<"Written", {
484
490
  readonly seconds: Schema.Number;
@@ -518,23 +524,17 @@ declare const JcfWebApi_base: HttpApi.HttpApi<"JcfWebApi", HttpApiGroup.HttpApiG
518
524
  readonly message: Schema.String;
519
525
  }>, Schema.TaggedStruct<"NotLoggedIn", {}>]>;
520
526
  readonly lines: Schema.$Array<Schema.String>;
521
- }>>, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError | typeof ProposalRejectedError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"describe", "POST", "/api/rows/describe", never, never, Schema.toCodecJson<Schema.Struct<{
522
- readonly planId: Schema.String;
523
- readonly rowId: Schema.String;
524
- }>>, never, Schema.toCodecJson<Schema.Struct<{
527
+ }>>, Schema.toCodecJson<typeof ApiError | typeof ProposalRejectedError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"confirm", "POST", "/api/rows/confirm", never, never, Schema.toCodecJson<Schema.Struct<{
528
+ readonly blocks: Schema.optional<Schema.$Array<Schema.Number>>;
529
+ readonly note: Schema.optional<Schema.String>;
525
530
  readonly planId: Schema.String;
526
531
  readonly rowId: Schema.String;
527
- readonly note: Schema.NullOr<Schema.String>;
528
- }>>, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"manual", "POST", "/api/rows/manual", never, never, Schema.toCodecJson<Schema.Struct<{
529
- readonly day: Schema.String;
530
- readonly note: Schema.optional<Schema.String>;
531
- readonly seconds: Schema.Number;
532
532
  readonly targets: Schema.optional<Schema.Struct<{
533
533
  readonly clockify: Schema.Boolean;
534
534
  readonly jira: Schema.Boolean;
535
535
  }>>;
536
- readonly startClock: Schema.optional<Schema.String>;
537
- readonly ticketKey: Schema.String;
536
+ readonly seconds: Schema.optional<Schema.Number>;
537
+ readonly ticketKey: Schema.optional<Schema.String>;
538
538
  }>>, never, Schema.toCodecJson<Schema.Struct<{
539
539
  readonly clockify: Schema.Union<readonly [Schema.TaggedStruct<"Written", {
540
540
  readonly seconds: Schema.Number;
@@ -574,7 +574,110 @@ declare const JcfWebApi_base: HttpApi.HttpApi<"JcfWebApi", HttpApiGroup.HttpApiG
574
574
  readonly message: Schema.String;
575
575
  }>, Schema.TaggedStruct<"NotLoggedIn", {}>]>;
576
576
  readonly lines: Schema.$Array<Schema.String>;
577
- }>>, Schema.toCodecJson<typeof ApiError | typeof ProposalRejectedError>, OwnerSessionAuth, never>, false> | HttpApiGroup.HttpApiGroup<"week", HttpApiEndpoint.HttpApiEndpoint<"read", "GET", "/api/week/", never, Schema.toCodecStringTree<Schema.Struct<{
577
+ }>>, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError | typeof ProposalRejectedError>, OwnerSessionAuth, never>, false> | HttpApiGroup.HttpApiGroup<"week", HttpApiEndpoint.HttpApiEndpoint<"saved", "GET", "/api/week/saved", never, Schema.toCodecStringTree<Schema.Struct<{
578
+ readonly monday: Schema.optional<Schema.String>;
579
+ readonly only: Schema.optional<Schema.Literals<readonly ["both", "clockify", "jira"]>>;
580
+ }>>, never, never, Schema.toCodecJson<Schema.Struct<{
581
+ readonly monday: Schema.String;
582
+ readonly scope: Schema.Literals<readonly ["both", "clockify", "jira"]>;
583
+ readonly plan: Schema.NullOr<Schema.Struct<{
584
+ readonly attributorAvailable: Schema.Boolean;
585
+ readonly attributorCalls: Schema.Number;
586
+ readonly days: Schema.$Array<Schema.String>;
587
+ readonly excludedDays: Schema.$Array<Schema.Struct<{
588
+ readonly day: Schema.String;
589
+ readonly reason: Schema.String;
590
+ }>>;
591
+ readonly monday: Schema.String;
592
+ readonly planId: Schema.String;
593
+ readonly rows: Schema.$Array<Schema.Struct<{
594
+ readonly clockifyDescription: Schema.NullOr<Schema.String>;
595
+ readonly clockifySeconds: Schema.Number;
596
+ readonly day: Schema.String;
597
+ readonly intervals: Schema.$Array<Schema.Struct<{
598
+ readonly entry: Schema.optionalKey<Schema.Struct<{
599
+ readonly revision: Schema.String;
600
+ readonly id: Schema.String;
601
+ readonly source: Schema.Literals<readonly ["jira", "clockify"]>;
602
+ readonly ticketKey: Schema.NullOr<Schema.String>;
603
+ readonly startMs: Schema.Number;
604
+ readonly endMs: Schema.Number;
605
+ readonly description: Schema.NullOr<Schema.String>;
606
+ }>>;
607
+ readonly endMs: Schema.Number;
608
+ readonly source: Schema.Literals<readonly ["clockify", "jira"]>;
609
+ readonly startMs: Schema.Number;
610
+ }>>;
611
+ readonly jiraSeconds: Schema.Number;
612
+ readonly proposal: Schema.optional<Schema.Struct<{
613
+ readonly activeSeconds: Schema.Number;
614
+ readonly blocks: Schema.$Array<Schema.Struct<{
615
+ readonly consumed: Schema.Struct<{
616
+ readonly clockify: Schema.Number;
617
+ readonly jira: Schema.Number;
618
+ }>;
619
+ readonly clockifyRefusal: Schema.optionalKey<Schema.Literal<"unlinked-overlap">>;
620
+ readonly endMs: Schema.Number;
621
+ readonly seconds: Schema.Number;
622
+ readonly startMs: Schema.Number;
623
+ readonly sourceStartMs: Schema.optional<Schema.Number>;
624
+ }>>;
625
+ readonly clockifyDelta: Schema.Number;
626
+ readonly confidence: Schema.NullOr<Schema.Number>;
627
+ readonly jiraDelta: Schema.Number;
628
+ readonly maxSeconds: Schema.Number;
629
+ readonly sessionCount: Schema.Number;
630
+ readonly signal: Schema.Literals<readonly ["branch", "path", "standing", "agent", "none"]>;
631
+ }>>;
632
+ readonly rowId: Schema.String;
633
+ readonly ticketKey: Schema.String;
634
+ readonly ticketTitle: Schema.NullOr<Schema.String>;
635
+ }>>;
636
+ readonly unlinkedClockify: Schema.$Array<Schema.Struct<{
637
+ readonly entry: Schema.optionalKey<Schema.Struct<{
638
+ readonly revision: Schema.String;
639
+ readonly id: Schema.String;
640
+ readonly source: Schema.Literals<readonly ["jira", "clockify"]>;
641
+ readonly ticketKey: Schema.NullOr<Schema.String>;
642
+ readonly startMs: Schema.Number;
643
+ readonly endMs: Schema.Number;
644
+ readonly description: Schema.NullOr<Schema.String>;
645
+ }>>;
646
+ readonly day: Schema.String;
647
+ readonly seconds: Schema.Number;
648
+ readonly description: Schema.NullOr<Schema.String>;
649
+ readonly startMs: Schema.Number;
650
+ readonly endMs: Schema.Number;
651
+ }>>;
652
+ readonly sessionCount: Schema.Number;
653
+ readonly sessionScanAvailable: Schema.optional<Schema.Boolean>;
654
+ readonly scope: Schema.Literals<readonly ["both", "clockify", "jira"]>;
655
+ readonly sessionRootCount: Schema.Number;
656
+ readonly notMine: Schema.$Array<Schema.Struct<{
657
+ readonly assignee: Schema.NullOr<Schema.String>;
658
+ readonly day: Schema.String;
659
+ readonly seconds: Schema.Number;
660
+ readonly signal: Schema.Literals<readonly ["branch", "path", "standing", "agent", "none"]>;
661
+ readonly ticketKey: Schema.String;
662
+ readonly ticketTitle: Schema.NullOr<Schema.String>;
663
+ }>>;
664
+ readonly ownershipChecked: Schema.Boolean;
665
+ readonly ownership: Schema.Literals<readonly ["assigned", "any"]>;
666
+ readonly unattributed: Schema.$Array<Schema.Struct<{
667
+ readonly cwds: Schema.$Array<Schema.String>;
668
+ readonly day: Schema.String;
669
+ readonly seconds: Schema.Number;
670
+ readonly sessionCount: Schema.Number;
671
+ }>>;
672
+ readonly withheld: Schema.$Array<Schema.Struct<{
673
+ readonly confidence: Schema.NullOr<Schema.Number>;
674
+ readonly day: Schema.String;
675
+ readonly seconds: Schema.Number;
676
+ readonly ticketKey: Schema.String;
677
+ readonly ticketTitle: Schema.NullOr<Schema.String>;
678
+ }>>;
679
+ }>>;
680
+ }>>, never, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"read", "GET", "/api/week/", never, Schema.toCodecStringTree<Schema.Struct<{
578
681
  readonly monday: Schema.optional<Schema.String>;
579
682
  readonly only: Schema.optional<Schema.Literals<readonly ["both", "clockify", "jira"]>>;
580
683
  }>>, never, never, Schema.toCodecJson<Schema.Struct<{
@@ -678,110 +781,7 @@ declare const JcfWebApi_base: HttpApi.HttpApi<"JcfWebApi", HttpApiGroup.HttpApiG
678
781
  }>>, never, never, HttpApiSchema.StreamUint8Array, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"recordedOnly", "GET", "/api/week/recorded-only", never, Schema.toCodecStringTree<Schema.Struct<{
679
782
  readonly monday: Schema.optional<Schema.String>;
680
783
  readonly only: Schema.optional<Schema.Literals<readonly ["both", "clockify", "jira"]>>;
681
- }>>, never, never, HttpApiSchema.StreamUint8Array, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"saved", "GET", "/api/week/saved", never, Schema.toCodecStringTree<Schema.Struct<{
682
- readonly monday: Schema.optional<Schema.String>;
683
- readonly only: Schema.optional<Schema.Literals<readonly ["both", "clockify", "jira"]>>;
684
- }>>, never, never, Schema.toCodecJson<Schema.Struct<{
685
- readonly monday: Schema.String;
686
- readonly scope: Schema.Literals<readonly ["both", "clockify", "jira"]>;
687
- readonly plan: Schema.NullOr<Schema.Struct<{
688
- readonly attributorAvailable: Schema.Boolean;
689
- readonly attributorCalls: Schema.Number;
690
- readonly days: Schema.$Array<Schema.String>;
691
- readonly excludedDays: Schema.$Array<Schema.Struct<{
692
- readonly day: Schema.String;
693
- readonly reason: Schema.String;
694
- }>>;
695
- readonly monday: Schema.String;
696
- readonly planId: Schema.String;
697
- readonly rows: Schema.$Array<Schema.Struct<{
698
- readonly clockifyDescription: Schema.NullOr<Schema.String>;
699
- readonly clockifySeconds: Schema.Number;
700
- readonly day: Schema.String;
701
- readonly intervals: Schema.$Array<Schema.Struct<{
702
- readonly entry: Schema.optionalKey<Schema.Struct<{
703
- readonly revision: Schema.String;
704
- readonly id: Schema.String;
705
- readonly source: Schema.Literals<readonly ["jira", "clockify"]>;
706
- readonly ticketKey: Schema.NullOr<Schema.String>;
707
- readonly startMs: Schema.Number;
708
- readonly endMs: Schema.Number;
709
- readonly description: Schema.NullOr<Schema.String>;
710
- }>>;
711
- readonly endMs: Schema.Number;
712
- readonly source: Schema.Literals<readonly ["clockify", "jira"]>;
713
- readonly startMs: Schema.Number;
714
- }>>;
715
- readonly jiraSeconds: Schema.Number;
716
- readonly proposal: Schema.optional<Schema.Struct<{
717
- readonly activeSeconds: Schema.Number;
718
- readonly blocks: Schema.$Array<Schema.Struct<{
719
- readonly consumed: Schema.Struct<{
720
- readonly clockify: Schema.Number;
721
- readonly jira: Schema.Number;
722
- }>;
723
- readonly clockifyRefusal: Schema.optionalKey<Schema.Literal<"unlinked-overlap">>;
724
- readonly endMs: Schema.Number;
725
- readonly seconds: Schema.Number;
726
- readonly startMs: Schema.Number;
727
- readonly sourceStartMs: Schema.optional<Schema.Number>;
728
- }>>;
729
- readonly clockifyDelta: Schema.Number;
730
- readonly confidence: Schema.NullOr<Schema.Number>;
731
- readonly jiraDelta: Schema.Number;
732
- readonly maxSeconds: Schema.Number;
733
- readonly sessionCount: Schema.Number;
734
- readonly signal: Schema.Literals<readonly ["branch", "path", "standing", "agent", "none"]>;
735
- }>>;
736
- readonly rowId: Schema.String;
737
- readonly ticketKey: Schema.String;
738
- readonly ticketTitle: Schema.NullOr<Schema.String>;
739
- }>>;
740
- readonly unlinkedClockify: Schema.$Array<Schema.Struct<{
741
- readonly entry: Schema.optionalKey<Schema.Struct<{
742
- readonly revision: Schema.String;
743
- readonly id: Schema.String;
744
- readonly source: Schema.Literals<readonly ["jira", "clockify"]>;
745
- readonly ticketKey: Schema.NullOr<Schema.String>;
746
- readonly startMs: Schema.Number;
747
- readonly endMs: Schema.Number;
748
- readonly description: Schema.NullOr<Schema.String>;
749
- }>>;
750
- readonly day: Schema.String;
751
- readonly seconds: Schema.Number;
752
- readonly description: Schema.NullOr<Schema.String>;
753
- readonly startMs: Schema.Number;
754
- readonly endMs: Schema.Number;
755
- }>>;
756
- readonly sessionCount: Schema.Number;
757
- readonly sessionScanAvailable: Schema.optional<Schema.Boolean>;
758
- readonly scope: Schema.Literals<readonly ["both", "clockify", "jira"]>;
759
- readonly sessionRootCount: Schema.Number;
760
- readonly notMine: Schema.$Array<Schema.Struct<{
761
- readonly assignee: Schema.NullOr<Schema.String>;
762
- readonly day: Schema.String;
763
- readonly seconds: Schema.Number;
764
- readonly signal: Schema.Literals<readonly ["branch", "path", "standing", "agent", "none"]>;
765
- readonly ticketKey: Schema.String;
766
- readonly ticketTitle: Schema.NullOr<Schema.String>;
767
- }>>;
768
- readonly ownershipChecked: Schema.Boolean;
769
- readonly ownership: Schema.Literals<readonly ["assigned", "any"]>;
770
- readonly unattributed: Schema.$Array<Schema.Struct<{
771
- readonly cwds: Schema.$Array<Schema.String>;
772
- readonly day: Schema.String;
773
- readonly seconds: Schema.Number;
774
- readonly sessionCount: Schema.Number;
775
- }>>;
776
- readonly withheld: Schema.$Array<Schema.Struct<{
777
- readonly confidence: Schema.NullOr<Schema.Number>;
778
- readonly day: Schema.String;
779
- readonly seconds: Schema.Number;
780
- readonly ticketKey: Schema.String;
781
- readonly ticketTitle: Schema.NullOr<Schema.String>;
782
- }>>;
783
- }>>;
784
- }>>, never, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"stream", "GET", "/api/week/stream", never, Schema.toCodecStringTree<Schema.Struct<{
784
+ }>>, never, never, HttpApiSchema.StreamUint8Array, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"stream", "GET", "/api/week/stream", never, Schema.toCodecStringTree<Schema.Struct<{
785
785
  readonly monday: Schema.optional<Schema.String>;
786
786
  readonly only: Schema.optional<Schema.Literals<readonly ["both", "clockify", "jira"]>>;
787
787
  }>>, never, never, HttpApiSchema.StreamUint8Array, Schema.toCodecJson<typeof ApiError | typeof PlanExpiredError>, OwnerSessionAuth, never>, false>>;
@@ -1,7 +1,18 @@
1
1
  /** The same authenticated routes, bootstrap exchange and static client in production and tests. */
2
- import { FileSystem, Layer, Path } from "effect";
3
- import { HttpRouter } from "effect/http";
4
- /** True only when `candidate` is `root` itself or one of its path-segment descendants. */
5
- export declare const isWithinDirectory: (path: Path.Path, root: string, candidate: string) => boolean;
2
+ import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
3
+ import { Layer, Path } from "effect";
4
+ /**
5
+ * Serves the built client at `root`, from the same origin as the API so the session cookie applies to
6
+ * both. Extensionless HTML navigations fall back to `index.html`; `no-cache` makes the browser
7
+ * revalidate, so a rebuilt `index.html` never points at hashed assets that no longer exist.
8
+ */
9
+ export declare const staticClient: (root: string) => Layer.Layer<never, import("effect/PlatformError").PlatformError, import("effect/FileSystem").FileSystem | import("effect/http/HttpPlatform").HttpPlatform | import("effect/http/HttpRouter").HttpRouter | Path.Path>;
10
+ /**
11
+ * The client `vite build` writes next to the compiled server. It reads host files, so give it the
12
+ * host's FileSystem even where the engine runs on another one.
13
+ */
14
+ export declare const StaticRouter: Layer.Layer<never, import("effect/PlatformError").BadArgument | import("effect/PlatformError").PlatformError, import("effect/FileSystem").FileSystem | import("effect/http/HttpPlatform").HttpPlatform | import("effect/http/HttpRouter").HttpRouter | Path.Path>;
15
+ /** The authenticated API and the bootstrap exchange: everything that runs on the engine's services. */
16
+ export declare const apiApplication: Layer.Layer<never, never, import("@knpkv/jira-clockify/services/ConfigService.js").ConfigService | import("effect/Crypto").Crypto | import("effect/FileSystem").FileSystem | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | import("effect/http/HttpRouter").HttpRouter | import("@knpkv/jira-clockify/services/IssueFacts.js").IssueFacts | import("@knpkv/jira-api-client").JiraApiClient | import("@knpkv/jira-cli/JiraAuth").JiraAuth | OwnerSession.OwnerSession | Path.Path | import("@knpkv/jira-clockify/services/ReconcileService.js").ReconcileService | import("effect/http/HttpRouter").Request<"Error", import("effect/http/HttpBody").HttpBodyError> | import("@knpkv/jira-clockify/services/SavedEntries.js").SavedEntries | import("@knpkv/jira-clockify/services/SessionAttributor.js").SessionAttributor>;
6
17
  /** Supply engine services and owner secrets; listener and platform belong to the executable. */
7
- export declare const application: Layer.Layer<never, never, import("@knpkv/jira-clockify/services/ConfigService.js").ConfigService | import("effect/Crypto").Crypto | FileSystem.FileSystem | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | HttpRouter.HttpRouter | import("@knpkv/jira-clockify/services/IssueFacts.js").IssueFacts | import("@knpkv/jira-api-client").JiraApiClient | import("@knpkv/jira-cli/JiraAuth").JiraAuth | import("./OwnerSession.js").OwnerSessionSecrets | Path.Path | import("@knpkv/jira-clockify/services/ReconcileService.js").ReconcileService | HttpRouter.Request<"Error", import("effect/PlatformError").BadArgument> | HttpRouter.Request<"Error", import("effect/http/HttpBody").HttpBodyError> | HttpRouter.Request<"Error", import("effect/PlatformError").PlatformError> | HttpRouter.Request<"Requires", FileSystem.FileSystem> | HttpRouter.Request<"Requires", import("./OwnerSession.js").OwnerSessionSecrets> | HttpRouter.Request<"Requires", Path.Path> | import("@knpkv/jira-clockify/services/SavedEntries.js").SavedEntries | import("@knpkv/jira-clockify/services/SessionAttributor.js").SessionAttributor>;
18
+ export declare const application: Layer.Layer<never, never, import("@knpkv/jira-clockify/services/ConfigService.js").ConfigService | import("effect/Crypto").Crypto | import("effect/FileSystem").FileSystem | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | import("effect/http/HttpRouter").HttpRouter | import("@knpkv/jira-clockify/services/IssueFacts.js").IssueFacts | import("@knpkv/jira-api-client").JiraApiClient | import("@knpkv/jira-cli/JiraAuth").JiraAuth | OwnerSession.OwnerSession | Path.Path | import("@knpkv/jira-clockify/services/ReconcileService.js").ReconcileService | import("effect/http/HttpRouter").Request<"Error", import("effect/http/HttpBody").HttpBodyError> | import("@knpkv/jira-clockify/services/SavedEntries.js").SavedEntries | import("@knpkv/jira-clockify/services/SessionAttributor.js").SessionAttributor>;
@@ -1,62 +1,29 @@
1
1
  /** The same authenticated routes, bootstrap exchange and static client in production and tests. */
2
- import { Effect, FileSystem, Layer, Path } from "effect";
3
- import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
2
+ import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
3
+ import { Effect, Layer, Path } from "effect";
4
+ import { HttpStaticServer } from "effect/http";
4
5
  import { HttpApiBuilder } from "effect/http-api";
5
6
  import { JcfWebApi } from "./Api.js";
6
7
  import { ConfigLive, EntriesLive, RowsLive, WeekLive } from "./Handlers.js";
7
- import { ownerSessionAuthLayer, OwnerSessionBootstrapRouter } from "./OwnerSession.js";
8
+ import { ownerSessionAuthLayer } from "./OwnerSession.js";
8
9
  import { layer as weekPlansLayer } from "./WeekPlans.js";
9
- const mimeTypes = new Map([
10
- [".css", "text/css"],
11
- [".html", "text/html"],
12
- [".ico", "image/x-icon"],
13
- [".js", "application/javascript"],
14
- [".json", "application/json"],
15
- [".map", "application/json"],
16
- [".svg", "image/svg+xml"],
17
- [".woff2", "font/woff2"]
18
- ]);
19
- /** True only when `candidate` is `root` itself or one of its path-segment descendants. */
20
- export const isWithinDirectory = (path, root, candidate) => {
21
- const relative = path.relative(root, candidate);
22
- return relative === "" || (!path.isAbsolute(relative) && relative !== ".." &&
23
- !relative.startsWith(`..${path.sep}`));
24
- };
25
- /** The built client, served from the same origin so the session cookie applies to both. */
26
- const serveStatic = Effect.gen(function* () {
27
- const request = yield* HttpServerRequest.HttpServerRequest;
28
- const fileSystem = yield* FileSystem.FileSystem;
10
+ /**
11
+ * Serves the built client at `root`, from the same origin as the API so the session cookie applies to
12
+ * both. Extensionless HTML navigations fall back to `index.html`; `no-cache` makes the browser
13
+ * revalidate, so a rebuilt `index.html` never points at hashed assets that no longer exist.
14
+ */
15
+ export const staticClient = (root) => HttpStaticServer.layer({ cacheControl: "no-cache", root, spa: true });
16
+ /**
17
+ * The client `vite build` writes next to the compiled server. It reads host files, so give it the
18
+ * host's FileSystem even where the engine runs on another one.
19
+ */
20
+ export const StaticRouter = Layer.unwrap(Effect.gen(function* () {
29
21
  const path = yield* Path.Path;
30
- const requested = decodeURIComponent(new URL(request.url, "http://localhost").pathname).replace(/^\/+/u, "");
31
- const here = yield* path.fromFileUrl(new URL(".", import.meta.url));
32
- const staticDirectory = path.resolve(here, "../../dist/client");
33
- const resolved = path.resolve(staticDirectory, requested === "" ? "index.html" : requested);
34
- // A resolved path that escaped the directory is a traversal attempt, not a missing file.
35
- if (!isWithinDirectory(path, staticDirectory, resolved)) {
36
- return HttpServerResponse.text("Forbidden", { status: 403 });
37
- }
38
- if (yield* fileSystem.exists(resolved)) {
39
- const info = yield* fileSystem.stat(resolved);
40
- if (info.type === "File") {
41
- return HttpServerResponse.uint8Array(yield* fileSystem.readFile(resolved), {
42
- headers: { "content-type": mimeTypes.get(path.extname(resolved)) ?? "application/octet-stream" },
43
- status: 200
44
- });
45
- }
46
- }
47
- const index = path.join(staticDirectory, "index.html");
48
- if (yield* fileSystem.exists(index)) {
49
- return HttpServerResponse.uint8Array(yield* fileSystem.readFile(index), {
50
- headers: { "content-type": "text/html" },
51
- status: 200
52
- });
53
- }
54
- return HttpServerResponse.text("The client has not been built. Run: pnpm --filter @knpkv/jcf-web build", {
55
- status: 404
56
- });
57
- });
58
- const StaticRouter = HttpRouter.use((router) => router.add("GET", "/*", serveStatic));
22
+ return staticClient(yield* path.fromFileUrl(new URL("../../dist/client", import.meta.url)));
23
+ }));
59
24
  const ApiRoutes = HttpApiBuilder.layer(JcfWebApi).pipe(Layer.provide(Layer.mergeAll(WeekLive, RowsLive, ConfigLive, EntriesLive)), Layer.provide(ownerSessionAuthLayer), Layer.provide(weekPlansLayer));
25
+ /** The authenticated API and the bootstrap exchange: everything that runs on the engine's services. */
26
+ export const apiApplication = Layer.mergeAll(ApiRoutes, OwnerSession.BootstrapRouter).pipe(Layer.orDie);
60
27
  /** Supply engine services and owner secrets; listener and platform belong to the executable. */
61
- export const application = Layer.mergeAll(ApiRoutes, OwnerSessionBootstrapRouter, StaticRouter).pipe(Layer.orDie);
28
+ export const application = Layer.mergeAll(apiApplication, StaticRouter).pipe(Layer.orDie);
62
29
  //# sourceMappingURL=HttpApplication.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"HttpApplication.js","sourceRoot":"","sources":["../../src/server/HttpApplication.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"HttpApplication.js","sourceRoot":"","sources":["../../src/server/HttpApplication.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAExD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAA;AAEnH;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;IAC7B,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,mBAAmB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAC7F,CAAC,CAAC,CAAC,CAAA;AAEH,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CACpD,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,EAC1E,KAAK,CAAC,OAAO,CAAC,qBAAqB,CAAC,EACpC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAC9B,CAAA;AAED,uGAAuG;AACvG,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AAEvG,gGAAgG;AAChG,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA"}
@@ -1,62 +1,17 @@
1
- import type { CsrfToken, PairingCode, SessionToken } from "@knpkv/browser-pairing/schema";
2
- import { Context, Effect, Layer, Redacted, Ref, Schema } from "effect";
3
- import { HttpRouter } from "effect/http";
4
- import { ForbiddenApiError, OwnerSessionAuth, UnauthorizedApiError } from "./Api.js";
5
- export interface BootstrapAttemptState {
6
- readonly failedAttempts: number;
7
- readonly inFlight: number;
8
- }
9
- export interface OwnerSessionSecretsContract {
10
- /** The origin this server considers itself to be. A request's Host header is never authoritative. */
11
- readonly authorityOrigin: string;
12
- /**
13
- * The origin the browser is expected to present. Same as the authority in production; the Vite
14
- * dev server when it proxies in front of it, because that is the origin the page runs on.
15
- */
16
- readonly browserOrigin: string;
17
- readonly bootstrapAvailable: Ref.Ref<boolean>;
18
- readonly bootstrapAttemptState: Ref.Ref<BootstrapAttemptState>;
19
- readonly bootstrapExpiresAtMillis: Ref.Ref<number | undefined>;
20
- readonly bootstrapToken: Redacted.Redacted<PairingCode>;
21
- readonly csrfToken: Redacted.Redacted<CsrfToken>;
22
- readonly ownerToken: Redacted.Redacted<SessionToken>;
23
- }
24
- declare const OwnerSessionSecrets_base: Context.ServiceClass<OwnerSessionSecrets, "@knpkv/jcf-web/OwnerSessionSecrets", OwnerSessionSecretsContract>;
25
- export declare class OwnerSessionSecrets extends OwnerSessionSecrets_base {
26
- }
27
- declare const UnsafeServerOriginError_base: Schema.Class<UnsafeServerOriginError, Schema.TaggedStruct<"UnsafeServerOriginError", {
28
- readonly message: Schema.String;
29
- readonly origin: Schema.String;
30
- }>, import("effect/Cause").YieldableError>;
31
- export declare class UnsafeServerOriginError extends UnsafeServerOriginError_base {
32
- }
33
- export declare const isLoopbackHostname: (hostname: string) => boolean;
34
- export declare const ownerSessionOrigin: (hostname: string, port: number) => string;
35
- export declare const requireLoopbackOrigin: (origin: string) => Effect.Effect<string, UnsafeServerOriginError, never>;
36
- /** The advertised origin: the bound server, or the dev server that proxies to it. */
37
- export declare const resolvePublicOrigin: (configuredOrigin: string | undefined, authorityOrigin: string) => Effect.Effect<string, UnsafeServerOriginError, never>;
38
- export declare const makeOwnerSessionSecrets: (authorityOrigin: string, configuredPublicOrigin?: string | undefined) => Effect.Effect<OwnerSessionSecretsContract, import("@knpkv/browser-pairing").BrowserPairingError | UnsafeServerOriginError, import("effect/Crypto").Crypto>;
39
- /** Start the bootstrap clock. Called once the server is actually listening, never before. */
40
- export declare const activateOwnerSessionBootstrap: (secrets: OwnerSessionSecretsContract) => Effect.Effect<void, import("@knpkv/browser-pairing").BrowserPairingError, never>;
41
1
  /**
42
- * The URL to open. The code rides in the fragment, which browsers do not send to servers and
43
- * proxies do not log, and the page strips it from the address bar once it has been spent.
2
+ * jcf-web's Owner Session: the shared `@knpkv/browser-pairing/owner-session` policy, with writes.
3
+ *
4
+ * The page saves timesheet entries, so writes need the CSRF token the bootstrap exchange returns. The
5
+ * Vite dev server keeps its own Origin when it proxies, so the configured public origin is also the
6
+ * origin every browser request is checked against: the URL that gets printed cannot be advertised and
7
+ * then rejected.
8
+ *
9
+ * @module
44
10
  */
45
- export declare const ownerSessionUrl: (publicOrigin: string, secrets: Pick<OwnerSessionSecretsContract, "bootstrapToken">) => string;
46
- export declare const ownerSessionCookie: (secrets: Pick<OwnerSessionSecretsContract, "ownerToken">) => string;
47
- interface OwnerRequest {
48
- readonly credential: string;
49
- readonly csrfToken: string | undefined;
50
- readonly fetchSite: string | undefined;
51
- readonly method: string;
52
- readonly origin: string | undefined;
53
- }
54
- export declare const authorizeOwnerRequest: (request: OwnerRequest, secrets: OwnerSessionSecretsContract) => Effect.Effect<undefined, ForbiddenApiError | UnauthorizedApiError, never>;
55
- export declare const authorizeBootstrapRequest: (request: {
56
- readonly authorization: string | undefined;
57
- readonly origin: string | undefined;
58
- }, secrets: OwnerSessionSecretsContract) => Effect.Effect<undefined, ForbiddenApiError | UnauthorizedApiError, never>;
59
- export declare const ownerSessionAuthLayer: Layer.Layer<OwnerSessionAuth, never, OwnerSessionSecrets>;
60
- /** The one route outside the authenticated API: spending the printed code for a session. */
61
- export declare const OwnerSessionBootstrapRouter: Layer.Layer<never, never, HttpRouter.HttpRouter | HttpRouter.Request<"Error", import("effect/http/HttpBody").HttpBodyError> | HttpRouter.Request<"Requires", OwnerSessionSecrets>>;
62
- export {};
11
+ import type { BrowserPairingError, CredentialCookieError } from "@knpkv/browser-pairing";
12
+ import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
13
+ import { type Crypto, Effect, Layer } from "effect";
14
+ import { OwnerSessionAuth } from "./Api.js";
15
+ /** Build the session for a server bound at `authorityOrigin`, advertised at `configuredPublicOrigin`. */
16
+ export declare const makeOwnerSession: (authorityOrigin: string, configuredPublicOrigin?: string | undefined) => Effect.Effect<OwnerSession.OwnerSessionService, BrowserPairingError | CredentialCookieError | OwnerSession.UnsafeLoopbackAddressError, Crypto.Crypto>;
17
+ export declare const ownerSessionAuthLayer: Layer.Layer<OwnerSessionAuth, never, OwnerSession.OwnerSession>;