@atproto/bsky 0.0.170 → 0.0.172
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/CHANGELOG.md +20 -0
- package/dist/api/app/bsky/notification/registerPush.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/registerPush.js +6 -7
- package/dist/api/app/bsky/notification/registerPush.js.map +1 -1
- package/dist/api/app/bsky/notification/unregisterPush.d.ts +4 -0
- package/dist/api/app/bsky/notification/unregisterPush.d.ts.map +1 -0
- package/dist/api/app/bsky/notification/unregisterPush.js +33 -0
- package/dist/api/app/bsky/notification/unregisterPush.js.map +1 -0
- package/dist/api/app/bsky/notification/util.d.ts +4 -0
- package/dist/api/app/bsky/notification/util.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/util.js +14 -1
- package/dist/api/app/bsky/notification/util.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getAgeAssuranceState.d.ts +4 -0
- package/dist/api/app/bsky/unspecced/getAgeAssuranceState.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/getAgeAssuranceState.js +36 -0
- package/dist/api/app/bsky/unspecced/getAgeAssuranceState.js.map +1 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.d.ts +4 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.js +59 -0
- package/dist/api/app/bsky/unspecced/initAgeAssurance.js.map +1 -0
- package/dist/api/external.d.ts +4 -0
- package/dist/api/external.d.ts.map +1 -0
- package/dist/api/external.js +47 -0
- package/dist/api/external.js.map +1 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +6 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/kws/api.d.ts +4 -0
- package/dist/api/kws/api.d.ts.map +1 -0
- package/dist/api/kws/api.js +60 -0
- package/dist/api/kws/api.js.map +1 -0
- package/dist/api/kws/index.d.ts +4 -0
- package/dist/api/kws/index.d.ts.map +1 -0
- package/dist/api/kws/index.js +21 -0
- package/dist/api/kws/index.js.map +1 -0
- package/dist/api/kws/types.d.ts +100 -0
- package/dist/api/kws/types.d.ts.map +1 -0
- package/dist/api/kws/types.js +29 -0
- package/dist/api/kws/types.js.map +1 -0
- package/dist/api/kws/util.d.ts +21 -0
- package/dist/api/kws/util.d.ts.map +1 -0
- package/dist/api/kws/util.js +78 -0
- package/dist/api/kws/util.js.map +1 -0
- package/dist/api/kws/webhook.d.ts +5 -0
- package/dist/api/kws/webhook.d.ts.map +1 -0
- package/dist/api/kws/webhook.js +80 -0
- package/dist/api/kws/webhook.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -0
- package/dist/context.js.map +1 -1
- package/dist/data-plane/bsync/index.d.ts.map +1 -1
- package/dist/data-plane/bsync/index.js +52 -33
- package/dist/data-plane/bsync/index.js.map +1 -1
- package/dist/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.js +22 -0
- package/dist/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.js.map +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/index.js +2 -0
- package/dist/data-plane/server/db/migrations/index.js.map +1 -1
- package/dist/data-plane/server/db/tables/actor.d.ts +2 -0
- package/dist/data-plane/server/db/tables/actor.d.ts.map +1 -1
- package/dist/data-plane/server/db/tables/actor.js.map +1 -1
- package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
- package/dist/data-plane/server/routes/profile.js +14 -1
- package/dist/data-plane/server/routes/profile.js.map +1 -1
- package/dist/feature-gates.d.ts +2 -1
- package/dist/feature-gates.d.ts.map +1 -1
- package/dist/feature-gates.js +1 -0
- package/dist/feature-gates.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/kws.d.ts +16 -0
- package/dist/kws.d.ts.map +1 -0
- package/dist/kws.js +86 -0
- package/dist/kws.js.map +1 -0
- package/dist/lexicon/index.d.ts +8 -2
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +16 -4
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +374 -82
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +191 -42
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/unregisterPush.d.ts +17 -0
- package/dist/lexicon/types/app/bsky/notification/unregisterPush.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/notification/unregisterPush.js +7 -0
- package/dist/lexicon/types/app/bsky/notification/unregisterPush.js.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts +32 -0
- package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/defs.js +18 -0
- package/dist/lexicon/types/app/bsky/unspecced/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.d.ts +18 -0
- package/dist/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.js +7 -0
- package/dist/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.js.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.d.ts +28 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.d.ts.map +1 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.js +7 -0
- package/dist/lexicon/types/app/bsky/unspecced/initAgeAssurance.js.map +1 -0
- package/dist/proto/bsky_pb.d.ts +33 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +112 -4
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/proto/courier_connect.d.ts +19 -1
- package/dist/proto/courier_connect.d.ts.map +1 -1
- package/dist/proto/courier_connect.js +18 -0
- package/dist/proto/courier_connect.js.map +1 -1
- package/dist/proto/courier_pb.d.ts +76 -0
- package/dist/proto/courier_pb.d.ts.map +1 -1
- package/dist/proto/courier_pb.js +233 -1
- package/dist/proto/courier_pb.js.map +1 -1
- package/dist/stash.d.ts +1 -0
- package/dist/stash.d.ts.map +1 -1
- package/dist/stash.js +1 -0
- package/dist/stash.js.map +1 -1
- package/package.json +7 -4
- package/proto/bsky.proto +8 -0
- package/proto/courier.proto +18 -0
- package/src/api/app/bsky/notification/registerPush.ts +5 -8
- package/src/api/app/bsky/notification/unregisterPush.ts +38 -0
- package/src/api/app/bsky/notification/util.ts +18 -0
- package/src/api/app/bsky/unspecced/getAgeAssuranceState.ts +46 -0
- package/src/api/app/bsky/unspecced/initAgeAssurance.ts +71 -0
- package/src/api/external.ts +13 -0
- package/src/api/index.ts +6 -0
- package/src/api/kws/api.ts +92 -0
- package/src/api/kws/index.ts +23 -0
- package/src/api/kws/types.ts +67 -0
- package/src/api/kws/util.ts +111 -0
- package/src/api/kws/webhook.ts +107 -0
- package/src/config.ts +59 -0
- package/src/context.ts +6 -0
- package/src/data-plane/bsync/index.ts +69 -33
- package/src/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.ts +22 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/tables/actor.ts +2 -0
- package/src/data-plane/server/routes/profile.ts +16 -1
- package/src/feature-gates.ts +1 -0
- package/src/index.ts +7 -1
- package/src/kws.ts +108 -0
- package/src/lexicon/index.ts +50 -11
- package/src/lexicon/lexicons.ts +201 -43
- package/src/lexicon/types/app/bsky/notification/unregisterPush.ts +36 -0
- package/src/lexicon/types/app/bsky/unspecced/defs.ts +50 -0
- package/src/lexicon/types/app/bsky/unspecced/getAgeAssuranceState.ts +34 -0
- package/src/lexicon/types/app/bsky/unspecced/initAgeAssurance.ts +47 -0
- package/src/proto/bsky_pb.ts +90 -0
- package/src/proto/courier_connect.ts +22 -0
- package/src/proto/courier_pb.ts +246 -0
- package/src/stash.ts +3 -0
- package/tests/views/age-assurance.test.ts +425 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
package/src/proto/courier_pb.ts
CHANGED
|
@@ -493,3 +493,249 @@ export class RegisterDeviceTokenResponse extends Message<RegisterDeviceTokenResp
|
|
|
493
493
|
return proto3.util.equals(RegisterDeviceTokenResponse, a, b)
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* @generated from message courier.UnregisterDeviceTokenRequest
|
|
499
|
+
*/
|
|
500
|
+
export class UnregisterDeviceTokenRequest extends Message<UnregisterDeviceTokenRequest> {
|
|
501
|
+
/**
|
|
502
|
+
* @generated from field: string did = 1;
|
|
503
|
+
*/
|
|
504
|
+
did = ''
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* @generated from field: string token = 2;
|
|
508
|
+
*/
|
|
509
|
+
token = ''
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* @generated from field: string app_id = 3;
|
|
513
|
+
*/
|
|
514
|
+
appId = ''
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* @generated from field: courier.AppPlatform platform = 4;
|
|
518
|
+
*/
|
|
519
|
+
platform = AppPlatform.UNSPECIFIED
|
|
520
|
+
|
|
521
|
+
constructor(data?: PartialMessage<UnregisterDeviceTokenRequest>) {
|
|
522
|
+
super()
|
|
523
|
+
proto3.util.initPartial(data, this)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
static readonly runtime: typeof proto3 = proto3
|
|
527
|
+
static readonly typeName = 'courier.UnregisterDeviceTokenRequest'
|
|
528
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
529
|
+
{ no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
530
|
+
{ no: 2, name: 'token', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
531
|
+
{ no: 3, name: 'app_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
532
|
+
{
|
|
533
|
+
no: 4,
|
|
534
|
+
name: 'platform',
|
|
535
|
+
kind: 'enum',
|
|
536
|
+
T: proto3.getEnumType(AppPlatform),
|
|
537
|
+
},
|
|
538
|
+
])
|
|
539
|
+
|
|
540
|
+
static fromBinary(
|
|
541
|
+
bytes: Uint8Array,
|
|
542
|
+
options?: Partial<BinaryReadOptions>,
|
|
543
|
+
): UnregisterDeviceTokenRequest {
|
|
544
|
+
return new UnregisterDeviceTokenRequest().fromBinary(bytes, options)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
static fromJson(
|
|
548
|
+
jsonValue: JsonValue,
|
|
549
|
+
options?: Partial<JsonReadOptions>,
|
|
550
|
+
): UnregisterDeviceTokenRequest {
|
|
551
|
+
return new UnregisterDeviceTokenRequest().fromJson(jsonValue, options)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
static fromJsonString(
|
|
555
|
+
jsonString: string,
|
|
556
|
+
options?: Partial<JsonReadOptions>,
|
|
557
|
+
): UnregisterDeviceTokenRequest {
|
|
558
|
+
return new UnregisterDeviceTokenRequest().fromJsonString(
|
|
559
|
+
jsonString,
|
|
560
|
+
options,
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
static equals(
|
|
565
|
+
a:
|
|
566
|
+
| UnregisterDeviceTokenRequest
|
|
567
|
+
| PlainMessage<UnregisterDeviceTokenRequest>
|
|
568
|
+
| undefined,
|
|
569
|
+
b:
|
|
570
|
+
| UnregisterDeviceTokenRequest
|
|
571
|
+
| PlainMessage<UnregisterDeviceTokenRequest>
|
|
572
|
+
| undefined,
|
|
573
|
+
): boolean {
|
|
574
|
+
return proto3.util.equals(UnregisterDeviceTokenRequest, a, b)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* @generated from message courier.UnregisterDeviceTokenResponse
|
|
580
|
+
*/
|
|
581
|
+
export class UnregisterDeviceTokenResponse extends Message<UnregisterDeviceTokenResponse> {
|
|
582
|
+
constructor(data?: PartialMessage<UnregisterDeviceTokenResponse>) {
|
|
583
|
+
super()
|
|
584
|
+
proto3.util.initPartial(data, this)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
static readonly runtime: typeof proto3 = proto3
|
|
588
|
+
static readonly typeName = 'courier.UnregisterDeviceTokenResponse'
|
|
589
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [])
|
|
590
|
+
|
|
591
|
+
static fromBinary(
|
|
592
|
+
bytes: Uint8Array,
|
|
593
|
+
options?: Partial<BinaryReadOptions>,
|
|
594
|
+
): UnregisterDeviceTokenResponse {
|
|
595
|
+
return new UnregisterDeviceTokenResponse().fromBinary(bytes, options)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
static fromJson(
|
|
599
|
+
jsonValue: JsonValue,
|
|
600
|
+
options?: Partial<JsonReadOptions>,
|
|
601
|
+
): UnregisterDeviceTokenResponse {
|
|
602
|
+
return new UnregisterDeviceTokenResponse().fromJson(jsonValue, options)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
static fromJsonString(
|
|
606
|
+
jsonString: string,
|
|
607
|
+
options?: Partial<JsonReadOptions>,
|
|
608
|
+
): UnregisterDeviceTokenResponse {
|
|
609
|
+
return new UnregisterDeviceTokenResponse().fromJsonString(
|
|
610
|
+
jsonString,
|
|
611
|
+
options,
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
static equals(
|
|
616
|
+
a:
|
|
617
|
+
| UnregisterDeviceTokenResponse
|
|
618
|
+
| PlainMessage<UnregisterDeviceTokenResponse>
|
|
619
|
+
| undefined,
|
|
620
|
+
b:
|
|
621
|
+
| UnregisterDeviceTokenResponse
|
|
622
|
+
| PlainMessage<UnregisterDeviceTokenResponse>
|
|
623
|
+
| undefined,
|
|
624
|
+
): boolean {
|
|
625
|
+
return proto3.util.equals(UnregisterDeviceTokenResponse, a, b)
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* @generated from message courier.SetAgeRestrictedRequest
|
|
631
|
+
*/
|
|
632
|
+
export class SetAgeRestrictedRequest extends Message<SetAgeRestrictedRequest> {
|
|
633
|
+
/**
|
|
634
|
+
* @generated from field: string did = 1;
|
|
635
|
+
*/
|
|
636
|
+
did = ''
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* @generated from field: bool age_restricted = 2;
|
|
640
|
+
*/
|
|
641
|
+
ageRestricted = false
|
|
642
|
+
|
|
643
|
+
constructor(data?: PartialMessage<SetAgeRestrictedRequest>) {
|
|
644
|
+
super()
|
|
645
|
+
proto3.util.initPartial(data, this)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
static readonly runtime: typeof proto3 = proto3
|
|
649
|
+
static readonly typeName = 'courier.SetAgeRestrictedRequest'
|
|
650
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
651
|
+
{ no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
652
|
+
{
|
|
653
|
+
no: 2,
|
|
654
|
+
name: 'age_restricted',
|
|
655
|
+
kind: 'scalar',
|
|
656
|
+
T: 8 /* ScalarType.BOOL */,
|
|
657
|
+
},
|
|
658
|
+
])
|
|
659
|
+
|
|
660
|
+
static fromBinary(
|
|
661
|
+
bytes: Uint8Array,
|
|
662
|
+
options?: Partial<BinaryReadOptions>,
|
|
663
|
+
): SetAgeRestrictedRequest {
|
|
664
|
+
return new SetAgeRestrictedRequest().fromBinary(bytes, options)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
static fromJson(
|
|
668
|
+
jsonValue: JsonValue,
|
|
669
|
+
options?: Partial<JsonReadOptions>,
|
|
670
|
+
): SetAgeRestrictedRequest {
|
|
671
|
+
return new SetAgeRestrictedRequest().fromJson(jsonValue, options)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
static fromJsonString(
|
|
675
|
+
jsonString: string,
|
|
676
|
+
options?: Partial<JsonReadOptions>,
|
|
677
|
+
): SetAgeRestrictedRequest {
|
|
678
|
+
return new SetAgeRestrictedRequest().fromJsonString(jsonString, options)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
static equals(
|
|
682
|
+
a:
|
|
683
|
+
| SetAgeRestrictedRequest
|
|
684
|
+
| PlainMessage<SetAgeRestrictedRequest>
|
|
685
|
+
| undefined,
|
|
686
|
+
b:
|
|
687
|
+
| SetAgeRestrictedRequest
|
|
688
|
+
| PlainMessage<SetAgeRestrictedRequest>
|
|
689
|
+
| undefined,
|
|
690
|
+
): boolean {
|
|
691
|
+
return proto3.util.equals(SetAgeRestrictedRequest, a, b)
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* @generated from message courier.SetAgeRestrictedResponse
|
|
697
|
+
*/
|
|
698
|
+
export class SetAgeRestrictedResponse extends Message<SetAgeRestrictedResponse> {
|
|
699
|
+
constructor(data?: PartialMessage<SetAgeRestrictedResponse>) {
|
|
700
|
+
super()
|
|
701
|
+
proto3.util.initPartial(data, this)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
static readonly runtime: typeof proto3 = proto3
|
|
705
|
+
static readonly typeName = 'courier.SetAgeRestrictedResponse'
|
|
706
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [])
|
|
707
|
+
|
|
708
|
+
static fromBinary(
|
|
709
|
+
bytes: Uint8Array,
|
|
710
|
+
options?: Partial<BinaryReadOptions>,
|
|
711
|
+
): SetAgeRestrictedResponse {
|
|
712
|
+
return new SetAgeRestrictedResponse().fromBinary(bytes, options)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
static fromJson(
|
|
716
|
+
jsonValue: JsonValue,
|
|
717
|
+
options?: Partial<JsonReadOptions>,
|
|
718
|
+
): SetAgeRestrictedResponse {
|
|
719
|
+
return new SetAgeRestrictedResponse().fromJson(jsonValue, options)
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
static fromJsonString(
|
|
723
|
+
jsonString: string,
|
|
724
|
+
options?: Partial<JsonReadOptions>,
|
|
725
|
+
): SetAgeRestrictedResponse {
|
|
726
|
+
return new SetAgeRestrictedResponse().fromJsonString(jsonString, options)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
static equals(
|
|
730
|
+
a:
|
|
731
|
+
| SetAgeRestrictedResponse
|
|
732
|
+
| PlainMessage<SetAgeRestrictedResponse>
|
|
733
|
+
| undefined,
|
|
734
|
+
b:
|
|
735
|
+
| SetAgeRestrictedResponse
|
|
736
|
+
| PlainMessage<SetAgeRestrictedResponse>
|
|
737
|
+
| undefined,
|
|
738
|
+
): boolean {
|
|
739
|
+
return proto3.util.equals(SetAgeRestrictedResponse, a, b)
|
|
740
|
+
}
|
|
741
|
+
}
|
package/src/stash.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Preferences,
|
|
6
6
|
SubjectActivitySubscription,
|
|
7
7
|
} from './lexicon/types/app/bsky/notification/defs'
|
|
8
|
+
import { AgeAssuranceEvent } from './lexicon/types/app/bsky/unspecced/defs'
|
|
8
9
|
import { Method } from './proto/bsync_pb'
|
|
9
10
|
|
|
10
11
|
type PickNSID<T extends { $type?: string }> = Exclude<T['$type'], undefined>
|
|
@@ -14,6 +15,8 @@ export const Namespaces = {
|
|
|
14
15
|
'app.bsky.notification.defs#preferences' satisfies PickNSID<Preferences>,
|
|
15
16
|
AppBskyNotificationDefsSubjectActivitySubscription:
|
|
16
17
|
'app.bsky.notification.defs#subjectActivitySubscription' satisfies PickNSID<SubjectActivitySubscription>,
|
|
18
|
+
AppBskyUnspeccedDefsAgeAssuranceEvent:
|
|
19
|
+
'app.bsky.unspecced.defs#ageAssuranceEvent' satisfies PickNSID<AgeAssuranceEvent>,
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export type Namespace = (typeof Namespaces)[keyof typeof Namespaces]
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
import { once } from 'node:events'
|
|
3
|
+
import { Server, createServer } from 'node:http'
|
|
4
|
+
import { AddressInfo } from 'node:net'
|
|
5
|
+
import express, { Application } from 'express'
|
|
6
|
+
import Statsig from 'statsig-node'
|
|
7
|
+
import { AtpAgent } from '@atproto/api'
|
|
8
|
+
import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env'
|
|
9
|
+
import {
|
|
10
|
+
KwsExternalPayload,
|
|
11
|
+
KwsVerificationQuery,
|
|
12
|
+
KwsWebhookBody,
|
|
13
|
+
} from '../../src/api/kws/types'
|
|
14
|
+
import {
|
|
15
|
+
parseExternalPayload,
|
|
16
|
+
serializeExternalPayload,
|
|
17
|
+
} from '../../src/api/kws/util'
|
|
18
|
+
import { GateID } from '../../src/feature-gates'
|
|
19
|
+
import { ids } from '../../src/lexicon/lexicons'
|
|
20
|
+
|
|
21
|
+
type Database = TestNetwork['bsky']['db']
|
|
22
|
+
|
|
23
|
+
describe('age assurance views', () => {
|
|
24
|
+
const verificationSecret = 'verificationSecret'
|
|
25
|
+
const webhookSecret = 'webhookSecret'
|
|
26
|
+
const attemptId = crypto.randomUUID()
|
|
27
|
+
const redirectUrl = 'https://bsky.app/intent/age-assurance'
|
|
28
|
+
|
|
29
|
+
let network: TestNetwork
|
|
30
|
+
let db: Database
|
|
31
|
+
let agent: AtpAgent
|
|
32
|
+
let sc: SeedClient
|
|
33
|
+
|
|
34
|
+
let actorDid: string
|
|
35
|
+
|
|
36
|
+
let kwsServer: MockKwsServer
|
|
37
|
+
const authMock = jest.fn()
|
|
38
|
+
const sendEmailMock = jest.fn()
|
|
39
|
+
|
|
40
|
+
beforeAll(async () => {
|
|
41
|
+
kwsServer = new MockKwsServer({
|
|
42
|
+
verificationSecret,
|
|
43
|
+
webhookSecret,
|
|
44
|
+
authMock,
|
|
45
|
+
sendEmailMock,
|
|
46
|
+
})
|
|
47
|
+
await kwsServer.listen()
|
|
48
|
+
|
|
49
|
+
network = await TestNetwork.create({
|
|
50
|
+
dbPostgresSchema: 'bsky_views_age_assurance',
|
|
51
|
+
bsky: {
|
|
52
|
+
statsigEnv: 'test',
|
|
53
|
+
statsigKey: 'secret-key',
|
|
54
|
+
kws: {
|
|
55
|
+
apiKey: 'apiKey',
|
|
56
|
+
apiOrigin: kwsServer.url,
|
|
57
|
+
authOrigin: kwsServer.url,
|
|
58
|
+
clientId: 'clientId',
|
|
59
|
+
redirectUrl,
|
|
60
|
+
userAgent: 'userAgent',
|
|
61
|
+
verificationSecret,
|
|
62
|
+
webhookSecret,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
Statsig.overrideGate(GateID.AgeAssurance, true)
|
|
67
|
+
db = network.bsky.db
|
|
68
|
+
agent = network.bsky.getClient()
|
|
69
|
+
sc = network.getSeedClient()
|
|
70
|
+
await basicSeed(sc)
|
|
71
|
+
await network.processAll()
|
|
72
|
+
|
|
73
|
+
actorDid = sc.dids.alice
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
beforeEach(async () => {
|
|
77
|
+
// Default mocks for KWS endpoints.
|
|
78
|
+
authMock.mockImplementation(
|
|
79
|
+
(_req: express.Request, res: express.Response) =>
|
|
80
|
+
res.json({
|
|
81
|
+
access_token:
|
|
82
|
+
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.INVALID',
|
|
83
|
+
expires_in: 3600,
|
|
84
|
+
}),
|
|
85
|
+
)
|
|
86
|
+
sendEmailMock.mockImplementation(
|
|
87
|
+
(_req: express.Request, res: express.Response) => {
|
|
88
|
+
res.json({})
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
afterEach(async () => {
|
|
94
|
+
jest.resetAllMocks()
|
|
95
|
+
await clearPrivateData(db)
|
|
96
|
+
await clearActorAgeAssurance(db)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
afterAll(async () => {
|
|
100
|
+
await network.close()
|
|
101
|
+
await kwsServer.stop()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const getAgeAssurance = async (did: string) => {
|
|
105
|
+
const { data } = await agent.app.bsky.unspecced.getAgeAssuranceState(
|
|
106
|
+
{},
|
|
107
|
+
{
|
|
108
|
+
headers: await network.serviceHeaders(
|
|
109
|
+
did,
|
|
110
|
+
ids.AppBskyUnspeccedGetAgeAssuranceState,
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
return data
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const initAgeAssurance = async (did: string, email?: string) => {
|
|
118
|
+
const { data } = await agent.app.bsky.unspecced.initAgeAssurance(
|
|
119
|
+
{
|
|
120
|
+
email: email ?? sc.accounts[did].email,
|
|
121
|
+
language: 'en',
|
|
122
|
+
countryCode: 'CC',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
headers: await network.serviceHeaders(
|
|
126
|
+
did,
|
|
127
|
+
ids.AppBskyUnspeccedInitAgeAssurance,
|
|
128
|
+
),
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
return data
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
describe('parsing external payload', () => {
|
|
135
|
+
it('fails if actorDid is missing', () => {
|
|
136
|
+
const serialized = JSON.stringify({
|
|
137
|
+
attemptId,
|
|
138
|
+
} satisfies Partial<KwsExternalPayload>)
|
|
139
|
+
|
|
140
|
+
expect(() => parseExternalPayload(serialized)).toThrow(
|
|
141
|
+
`Invalid external payload`,
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('fails if attemptId is missing', () => {
|
|
146
|
+
const serialized = JSON.stringify({
|
|
147
|
+
actorDid,
|
|
148
|
+
} satisfies Partial<KwsExternalPayload>)
|
|
149
|
+
|
|
150
|
+
expect(() => parseExternalPayload(serialized)).toThrow(
|
|
151
|
+
`Invalid external payload`,
|
|
152
|
+
)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('fails if extra field is present', () => {
|
|
156
|
+
const serialized = JSON.stringify({
|
|
157
|
+
actorDid,
|
|
158
|
+
attemptId,
|
|
159
|
+
extra: 'field',
|
|
160
|
+
} satisfies KwsExternalPayload & { extra: string })
|
|
161
|
+
|
|
162
|
+
expect(() => parseExternalPayload(serialized)).toThrow(
|
|
163
|
+
`Invalid external payload`,
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('does not fail if all fields are set', () => {
|
|
168
|
+
const externalPayload: KwsExternalPayload = {
|
|
169
|
+
actorDid,
|
|
170
|
+
attemptId,
|
|
171
|
+
}
|
|
172
|
+
const serialized = JSON.stringify(externalPayload)
|
|
173
|
+
|
|
174
|
+
const parsed = parseExternalPayload(serialized)
|
|
175
|
+
expect(parsed).toStrictEqual(externalPayload)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('fetches AA state correctly if user never did the flow', async () => {
|
|
180
|
+
const aliceState = await getAgeAssurance(actorDid)
|
|
181
|
+
expect(aliceState).toEqual({
|
|
182
|
+
status: 'unknown',
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('validates email used for AA flow', async () => {
|
|
187
|
+
await expect(initAgeAssurance(actorDid, 'invalid-email')).rejects.toThrow(
|
|
188
|
+
'This email address is not supported,',
|
|
189
|
+
)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('verification response flow', () => {
|
|
193
|
+
it('performs the AA flow', async () => {
|
|
194
|
+
const state0 = await getAgeAssurance(actorDid)
|
|
195
|
+
expect(state0).toStrictEqual({
|
|
196
|
+
status: 'unknown',
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const state1 = await initAgeAssurance(actorDid)
|
|
200
|
+
expect(state1).toStrictEqual({
|
|
201
|
+
status: 'pending',
|
|
202
|
+
lastInitiatedAt: expect.any(String),
|
|
203
|
+
})
|
|
204
|
+
expect(sendEmailMock).toHaveBeenCalledTimes(1)
|
|
205
|
+
|
|
206
|
+
const externalPayload: KwsExternalPayload = {
|
|
207
|
+
actorDid,
|
|
208
|
+
attemptId,
|
|
209
|
+
}
|
|
210
|
+
const status = { verified: true }
|
|
211
|
+
const verificationRes = await kwsServer.callVerificationResponse(
|
|
212
|
+
network.bsky.url,
|
|
213
|
+
{ externalPayload, status },
|
|
214
|
+
)
|
|
215
|
+
expect(verificationRes.status).toBe(302)
|
|
216
|
+
expect(verificationRes.headers.get('Location')).toBe(
|
|
217
|
+
`${redirectUrl}?actorDid=${encodeURIComponent(actorDid)}&result=success`,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
const state2 = await getAgeAssurance(actorDid)
|
|
221
|
+
expect(state2).toStrictEqual({
|
|
222
|
+
status: 'assured',
|
|
223
|
+
lastInitiatedAt: expect.any(String),
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('does not assure if the verification response has status not verified', async () => {
|
|
228
|
+
await initAgeAssurance(actorDid)
|
|
229
|
+
|
|
230
|
+
const externalPayload: KwsExternalPayload = {
|
|
231
|
+
actorDid,
|
|
232
|
+
attemptId,
|
|
233
|
+
}
|
|
234
|
+
const status = { verified: false }
|
|
235
|
+
const verificationRes = await kwsServer.callVerificationResponse(
|
|
236
|
+
network.bsky.url,
|
|
237
|
+
{ externalPayload, status },
|
|
238
|
+
)
|
|
239
|
+
expect(verificationRes.status).toBe(302)
|
|
240
|
+
expect(verificationRes.headers.get('Location')).toBe(
|
|
241
|
+
`${redirectUrl}?result=unknown`,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
const state = await getAgeAssurance(actorDid)
|
|
245
|
+
expect(state).toStrictEqual({
|
|
246
|
+
status: 'pending',
|
|
247
|
+
lastInitiatedAt: expect.any(String),
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
describe('webhook flow', () => {
|
|
253
|
+
it('performs the AA flow', async () => {
|
|
254
|
+
const state0 = await getAgeAssurance(actorDid)
|
|
255
|
+
expect(state0).toStrictEqual({
|
|
256
|
+
status: 'unknown',
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const state1 = await initAgeAssurance(actorDid)
|
|
260
|
+
expect(state1).toStrictEqual({
|
|
261
|
+
status: 'pending',
|
|
262
|
+
lastInitiatedAt: expect.any(String),
|
|
263
|
+
})
|
|
264
|
+
expect(sendEmailMock).toHaveBeenCalledTimes(1)
|
|
265
|
+
|
|
266
|
+
const webhookRes = await kwsServer.callWebhook(network.bsky.url, {
|
|
267
|
+
payload: {
|
|
268
|
+
externalPayload: {
|
|
269
|
+
actorDid,
|
|
270
|
+
attemptId,
|
|
271
|
+
},
|
|
272
|
+
status: {
|
|
273
|
+
verified: true,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
})
|
|
277
|
+
expect(webhookRes.status).toBe(200)
|
|
278
|
+
|
|
279
|
+
const state2 = await getAgeAssurance(actorDid)
|
|
280
|
+
expect(state2).toStrictEqual({
|
|
281
|
+
status: 'assured',
|
|
282
|
+
lastInitiatedAt: expect.any(String),
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('does not assure if the webhook has status not verified', async () => {
|
|
287
|
+
await initAgeAssurance(actorDid)
|
|
288
|
+
|
|
289
|
+
const webhookRes = await kwsServer.callWebhook(network.bsky.url, {
|
|
290
|
+
payload: {
|
|
291
|
+
externalPayload: {
|
|
292
|
+
actorDid,
|
|
293
|
+
attemptId,
|
|
294
|
+
},
|
|
295
|
+
status: {
|
|
296
|
+
verified: false,
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
expect(webhookRes.status).toBe(500)
|
|
301
|
+
|
|
302
|
+
const state = await getAgeAssurance(actorDid)
|
|
303
|
+
expect(state).toStrictEqual({
|
|
304
|
+
status: 'pending',
|
|
305
|
+
lastInitiatedAt: expect.any(String),
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
const clearPrivateData = async (db: Database) => {
|
|
312
|
+
await db.db.deleteFrom('private_data').execute()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const clearActorAgeAssurance = async (db: Database) => {
|
|
316
|
+
await db.db
|
|
317
|
+
.updateTable('actor')
|
|
318
|
+
.set({
|
|
319
|
+
ageAssuranceStatus: null,
|
|
320
|
+
ageAssuranceLastInitiatedAt: null,
|
|
321
|
+
})
|
|
322
|
+
.execute()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
class MockKwsServer {
|
|
326
|
+
private verificationSecret: string
|
|
327
|
+
private webhookSecret: string
|
|
328
|
+
private app: Application
|
|
329
|
+
private server: Server
|
|
330
|
+
|
|
331
|
+
constructor({
|
|
332
|
+
verificationSecret,
|
|
333
|
+
webhookSecret,
|
|
334
|
+
authMock,
|
|
335
|
+
sendEmailMock,
|
|
336
|
+
}: {
|
|
337
|
+
verificationSecret: string
|
|
338
|
+
webhookSecret: string
|
|
339
|
+
authMock: jest.Mock
|
|
340
|
+
sendEmailMock: jest.Mock
|
|
341
|
+
}) {
|
|
342
|
+
this.verificationSecret = verificationSecret
|
|
343
|
+
this.webhookSecret = webhookSecret
|
|
344
|
+
|
|
345
|
+
this.app = express()
|
|
346
|
+
.post('/auth/realms/kws/protocol/openid-connect/token', (req, res) =>
|
|
347
|
+
authMock(req, res),
|
|
348
|
+
)
|
|
349
|
+
.post('/v1/verifications/send-email', (req, res) =>
|
|
350
|
+
sendEmailMock(req, res),
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
this.server = createServer(this.app)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async listen(port?: number) {
|
|
357
|
+
this.server.listen(port)
|
|
358
|
+
await once(this.server, 'listening')
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async stop() {
|
|
362
|
+
this.server.close()
|
|
363
|
+
await once(this.server, 'close')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
callVerificationResponse(
|
|
367
|
+
bskyUrl: string,
|
|
368
|
+
query: Omit<KwsVerificationQuery, 'signature'>,
|
|
369
|
+
) {
|
|
370
|
+
const externalPayloadJson = JSON.stringify(query.externalPayload)
|
|
371
|
+
const statusJson = JSON.stringify(query.status)
|
|
372
|
+
|
|
373
|
+
const sig = crypto
|
|
374
|
+
.createHmac('sha256', this.verificationSecret)
|
|
375
|
+
.update(`${statusJson}:${externalPayloadJson}`)
|
|
376
|
+
.digest('hex')
|
|
377
|
+
|
|
378
|
+
const queryString = new URLSearchParams({
|
|
379
|
+
externalPayload: externalPayloadJson,
|
|
380
|
+
signature: sig,
|
|
381
|
+
status: statusJson,
|
|
382
|
+
}).toString()
|
|
383
|
+
|
|
384
|
+
return fetch(
|
|
385
|
+
`${bskyUrl}/external/kws/age-assurance-verification?${queryString}`,
|
|
386
|
+
{
|
|
387
|
+
method: 'GET',
|
|
388
|
+
redirect: 'manual',
|
|
389
|
+
},
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
callWebhook(bskyUrl: string, body: KwsWebhookBody): Promise<Response> {
|
|
394
|
+
const withSerializedExternalPayload = {
|
|
395
|
+
...body,
|
|
396
|
+
payload: {
|
|
397
|
+
...body.payload,
|
|
398
|
+
externalPayload: serializeExternalPayload(body.payload.externalPayload),
|
|
399
|
+
},
|
|
400
|
+
}
|
|
401
|
+
const bodyBuffer = Buffer.from(
|
|
402
|
+
JSON.stringify(withSerializedExternalPayload),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
const timestamp = new Date().valueOf()
|
|
406
|
+
const sig = crypto
|
|
407
|
+
.createHmac('sha256', this.webhookSecret)
|
|
408
|
+
.update(`${timestamp}.${bodyBuffer}`)
|
|
409
|
+
.digest('hex')
|
|
410
|
+
|
|
411
|
+
return fetch(`${bskyUrl}/external/kws/age-assurance-webhook`, {
|
|
412
|
+
method: 'POST',
|
|
413
|
+
body: bodyBuffer,
|
|
414
|
+
headers: {
|
|
415
|
+
'x-kws-signature': `t=${timestamp},v1=${sig}`,
|
|
416
|
+
'Content-Type': 'application/json',
|
|
417
|
+
},
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
get url() {
|
|
422
|
+
const address = this.server.address() as AddressInfo
|
|
423
|
+
return `http://localhost:${address.port}`
|
|
424
|
+
}
|
|
425
|
+
}
|