@atproto/bsky 0.0.201 → 0.0.203
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 +12 -0
- package/dist/api/age-assurance/const.d.ts.map +1 -1
- package/dist/api/age-assurance/const.js +20 -0
- package/dist/api/age-assurance/const.js.map +1 -1
- package/dist/api/app/bsky/ageassurance/begin.d.ts.map +1 -1
- package/dist/api/app/bsky/ageassurance/begin.js +15 -8
- package/dist/api/app/bsky/ageassurance/begin.js.map +1 -1
- package/dist/api/app/bsky/contact/dismissMatch.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/dismissMatch.js +2 -3
- package/dist/api/app/bsky/contact/dismissMatch.js.map +1 -1
- package/dist/api/app/bsky/contact/getMatches.js +2 -3
- package/dist/api/app/bsky/contact/getMatches.js.map +1 -1
- package/dist/api/app/bsky/contact/getSyncStatus.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/getSyncStatus.js +2 -3
- package/dist/api/app/bsky/contact/getSyncStatus.js.map +1 -1
- package/dist/api/app/bsky/contact/importContacts.js +2 -3
- package/dist/api/app/bsky/contact/importContacts.js.map +1 -1
- package/dist/api/app/bsky/contact/removeData.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/removeData.js +2 -3
- package/dist/api/app/bsky/contact/removeData.js.map +1 -1
- package/dist/api/app/bsky/contact/sendNotification.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/sendNotification.js.map +1 -1
- package/dist/api/app/bsky/contact/startPhoneVerification.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/startPhoneVerification.js +2 -3
- package/dist/api/app/bsky/contact/startPhoneVerification.js.map +1 -1
- package/dist/api/app/bsky/contact/util.d.ts +7 -0
- package/dist/api/app/bsky/contact/util.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/util.js +68 -0
- package/dist/api/app/bsky/contact/util.js.map +1 -1
- package/dist/api/app/bsky/contact/verifyPhone.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/verifyPhone.js +2 -3
- package/dist/api/app/bsky/contact/verifyPhone.js.map +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +4 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/sitemap.d.ts +4 -0
- package/dist/api/sitemap.d.ts.map +1 -0
- package/dist/api/sitemap.js +67 -0
- package/dist/api/sitemap.js.map +1 -0
- package/dist/data-plane/server/routes/index.d.ts.map +1 -1
- package/dist/data-plane/server/routes/index.js +2 -0
- package/dist/data-plane/server/routes/index.js.map +1 -1
- package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
- package/dist/data-plane/server/routes/profile.js +10 -8
- package/dist/data-plane/server/routes/profile.js.map +1 -1
- package/dist/data-plane/server/routes/sitemap.d.ts +5 -0
- package/dist/data-plane/server/routes/sitemap.d.ts.map +1 -0
- package/dist/data-plane/server/routes/sitemap.js +38 -0
- package/dist/data-plane/server/routes/sitemap.js.map +1 -0
- package/dist/hydration/actor.js +1 -1
- package/dist/hydration/actor.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +100 -46
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +67 -22
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/dismissMatch.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/contact/dismissMatch.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/dismissMatch.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/getMatches.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/contact/getMatches.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/getMatches.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/getSyncStatus.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/contact/getSyncStatus.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/getSyncStatus.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/importContacts.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/contact/importContacts.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/importContacts.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/removeData.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/contact/removeData.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/removeData.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/startPhoneVerification.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/contact/startPhoneVerification.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/startPhoneVerification.js.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/verifyPhone.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/contact/verifyPhone.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/contact/verifyPhone.js.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/notification/listNotifications.js.map +1 -1
- package/dist/proto/bsky_connect.d.ts +21 -1
- package/dist/proto/bsky_connect.d.ts.map +1 -1
- package/dist/proto/bsky_connect.js +20 -0
- package/dist/proto/bsky_connect.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +97 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +256 -5
- package/dist/proto/bsky_pb.js.map +1 -1
- package/package.json +7 -7
- package/proto/bsky.proto +31 -0
- package/src/api/age-assurance/const.ts +20 -0
- package/src/api/app/bsky/ageassurance/begin.ts +21 -11
- package/src/api/app/bsky/contact/dismissMatch.ts +7 -6
- package/src/api/app/bsky/contact/getMatches.ts +8 -7
- package/src/api/app/bsky/contact/getSyncStatus.ts +6 -5
- package/src/api/app/bsky/contact/importContacts.ts +8 -7
- package/src/api/app/bsky/contact/removeData.ts +6 -5
- package/src/api/app/bsky/contact/sendNotification.ts +2 -1
- package/src/api/app/bsky/contact/startPhoneVerification.ts +7 -6
- package/src/api/app/bsky/contact/util.ts +80 -1
- package/src/api/app/bsky/contact/verifyPhone.ts +8 -7
- package/src/api/index.ts +4 -0
- package/src/api/sitemap.ts +76 -0
- package/src/data-plane/server/routes/index.ts +2 -0
- package/src/data-plane/server/routes/profile.ts +8 -6
- package/src/data-plane/server/routes/sitemap.ts +43 -0
- package/src/hydration/actor.ts +1 -1
- package/src/index.ts +6 -1
- package/src/lexicon/lexicons.ts +67 -22
- package/src/lexicon/types/app/bsky/contact/dismissMatch.ts +1 -1
- package/src/lexicon/types/app/bsky/contact/getMatches.ts +1 -1
- package/src/lexicon/types/app/bsky/contact/getSyncStatus.ts +1 -1
- package/src/lexicon/types/app/bsky/contact/importContacts.ts +6 -1
- package/src/lexicon/types/app/bsky/contact/removeData.ts +1 -1
- package/src/lexicon/types/app/bsky/contact/startPhoneVerification.ts +1 -1
- package/src/lexicon/types/app/bsky/contact/verifyPhone.ts +6 -1
- package/src/lexicon/types/app/bsky/notification/listNotifications.ts +1 -0
- package/src/proto/bsky_connect.ts +21 -1
- package/src/proto/bsky_pb.ts +188 -0
- package/tests/sitemap.test.ts +75 -0
- package/tests/views/age-assurance-v2.test.ts +51 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/bsky",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.203",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"undici": "^6.19.8",
|
|
53
53
|
"zod": "3.23.8",
|
|
54
54
|
"@atproto-labs/fetch-node": "0.2.0",
|
|
55
|
-
"@atproto-labs/xrpc-utils": "0.0.24",
|
|
56
|
-
"@atproto/api": "^0.18.6",
|
|
57
55
|
"@atproto/common": "^0.5.3",
|
|
56
|
+
"@atproto-labs/xrpc-utils": "0.0.24",
|
|
57
|
+
"@atproto/api": "^0.18.8",
|
|
58
58
|
"@atproto/crypto": "^0.4.5",
|
|
59
59
|
"@atproto/did": "^0.2.3",
|
|
60
60
|
"@atproto/identity": "^0.4.10",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@atproto/repo": "^0.8.12",
|
|
63
63
|
"@atproto/syntax": "^0.4.2",
|
|
64
64
|
"@atproto/sync": "^0.1.39",
|
|
65
|
-
"@atproto/xrpc-server": "^0.10.
|
|
65
|
+
"@atproto/xrpc-server": "^0.10.4"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@bufbuild/buf": "^1.28.1",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"jest": "^28.1.2",
|
|
79
79
|
"ts-node": "^10.8.2",
|
|
80
80
|
"typescript": "^5.6.3",
|
|
81
|
+
"@atproto/api": "^0.18.8",
|
|
81
82
|
"@atproto/lex-cli": "^0.9.8",
|
|
82
|
-
"@atproto/
|
|
83
|
-
"@atproto/pds": "^0.4.199"
|
|
84
|
-
"@atproto/xrpc": "^0.7.7"
|
|
83
|
+
"@atproto/xrpc": "^0.7.7",
|
|
84
|
+
"@atproto/pds": "^0.4.199"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"codegen": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/*",
|
package/proto/bsky.proto
CHANGED
|
@@ -1310,6 +1310,33 @@ message GetFollowsFollowingResponse {
|
|
|
1310
1310
|
repeated FollowsFollowing results = 1;
|
|
1311
1311
|
}
|
|
1312
1312
|
|
|
1313
|
+
message GetSitemapIndexRequest {
|
|
1314
|
+
SitemapPageType type = 1;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
message GetSitemapIndexResponse {
|
|
1318
|
+
// GZIP compressed XML sitemap
|
|
1319
|
+
bytes sitemap = 1;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Sitemap HTTP paths are typically of the form `/type/yyyy-mm-dd/N.xml.gz`, i.e. `/users/2025-01-01/1.xml.gz`
|
|
1323
|
+
message GetSitemapPageRequest {
|
|
1324
|
+
SitemapPageType type = 1;
|
|
1325
|
+
google.protobuf.Timestamp date = 2;
|
|
1326
|
+
// One-indexed
|
|
1327
|
+
int32 bucket = 3;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
enum SitemapPageType {
|
|
1331
|
+
SITEMAP_PAGE_TYPE_UNSPECIFIED = 0;
|
|
1332
|
+
SITEMAP_PAGE_TYPE_USER = 1;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
message GetSitemapPageResponse {
|
|
1336
|
+
// GZIP compressed XML sitemap
|
|
1337
|
+
bytes sitemap = 1;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1313
1340
|
// Ping
|
|
1314
1341
|
message PingRequest {}
|
|
1315
1342
|
message PingResponse {}
|
|
@@ -1470,6 +1497,10 @@ service Service {
|
|
|
1470
1497
|
// Graph
|
|
1471
1498
|
rpc GetFollowsFollowing(GetFollowsFollowingRequest) returns (GetFollowsFollowingResponse);
|
|
1472
1499
|
|
|
1500
|
+
// Sitemaps
|
|
1501
|
+
rpc GetSitemapIndex(GetSitemapIndexRequest) returns (GetSitemapIndexResponse);
|
|
1502
|
+
rpc GetSitemapPage(GetSitemapPageRequest) returns (GetSitemapPageResponse);
|
|
1503
|
+
|
|
1473
1504
|
// Ping
|
|
1474
1505
|
rpc Ping(PingRequest) returns (PingResponse);
|
|
1475
1506
|
|
|
@@ -138,5 +138,25 @@ export const AGE_ASSURANCE_CONFIG: AppBskyAgeassuranceDefs.Config = {
|
|
|
138
138
|
},
|
|
139
139
|
],
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
countryCode: 'US',
|
|
143
|
+
regionCode: 'TN',
|
|
144
|
+
rules: [
|
|
145
|
+
{
|
|
146
|
+
$type: ids.IfAssuredOverAge,
|
|
147
|
+
age: 18,
|
|
148
|
+
access: 'full',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
$type: ids.IfDeclaredOverAge,
|
|
152
|
+
age: 18,
|
|
153
|
+
access: 'full',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
$type: ids.Default,
|
|
157
|
+
access: 'none',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
141
161
|
],
|
|
142
162
|
}
|
|
@@ -37,14 +37,14 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
37
37
|
|
|
38
38
|
const actorDid = auth.credentials.iss
|
|
39
39
|
const actorInfo = await getAgeVerificationState(ctx, actorDid)
|
|
40
|
+
const existingStatus = actorInfo?.ageAssuranceStatus?.status
|
|
41
|
+
const existingAccess = actorInfo?.ageAssuranceStatus?.access
|
|
40
42
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
}
|
|
43
|
+
if (existingStatus === 'blocked') {
|
|
44
|
+
throw new InvalidRequestError(
|
|
45
|
+
`Cannot initiate age assurance flow from current state: ${existingStatus}`,
|
|
46
|
+
'InvalidInitiation',
|
|
47
|
+
)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const attemptId = crypto.randomUUID()
|
|
@@ -107,14 +107,24 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
107
107
|
})
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// If we have existing status/access for this region, retain it.
|
|
111
|
+
const nextStatus =
|
|
112
|
+
existingStatus && existingStatus !== 'unknown'
|
|
113
|
+
? existingStatus
|
|
114
|
+
: 'pending'
|
|
115
|
+
const nextAccess =
|
|
116
|
+
existingAccess && existingAccess !== 'unknown'
|
|
117
|
+
? existingAccess
|
|
118
|
+
: 'unknown'
|
|
119
|
+
|
|
110
120
|
const event = await createEvent(ctx, actorDid, {
|
|
111
121
|
attemptId,
|
|
112
122
|
email,
|
|
113
123
|
// Assumes `app.set('trust proxy', ...)` configured with `true` or specific values.
|
|
114
124
|
initIp: req.ip,
|
|
115
125
|
initUa: getClientUa(req),
|
|
116
|
-
status:
|
|
117
|
-
access:
|
|
126
|
+
status: nextStatus,
|
|
127
|
+
access: nextAccess,
|
|
118
128
|
countryCode,
|
|
119
129
|
regionCode,
|
|
120
130
|
})
|
|
@@ -123,8 +133,8 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
123
133
|
encoding: 'application/json',
|
|
124
134
|
body: {
|
|
125
135
|
lastInitiatedAt: event.createdAt,
|
|
126
|
-
status:
|
|
127
|
-
access:
|
|
136
|
+
status: nextStatus,
|
|
137
|
+
access: nextAccess,
|
|
128
138
|
},
|
|
129
139
|
}
|
|
130
140
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppContext } from '../../../../context'
|
|
2
2
|
import { Server } from '../../../../lexicon'
|
|
3
|
-
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
3
|
+
import { assertRolodexOrThrowUnimplemented, callRolodexClient } from './util'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
6
|
server.app.bsky.contact.dismissMatch({
|
|
@@ -9,11 +9,12 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
9
9
|
assertRolodexOrThrowUnimplemented(ctx)
|
|
10
10
|
|
|
11
11
|
const actor = auth.credentials.iss
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
await callRolodexClient(
|
|
13
|
+
ctx.rolodexClient.dismissMatch({
|
|
14
|
+
actor,
|
|
15
|
+
subject: input.body.subject,
|
|
16
|
+
}),
|
|
17
|
+
)
|
|
17
18
|
|
|
18
19
|
return {
|
|
19
20
|
encoding: 'application/json',
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from '../../../../pipeline'
|
|
15
15
|
import { RolodexClient } from '../../../../rolodex'
|
|
16
16
|
import { Views } from '../../../../views'
|
|
17
|
-
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
17
|
+
import { assertRolodexOrThrowUnimplemented, callRolodexClient } from './util'
|
|
18
18
|
|
|
19
19
|
export default function (server: Server, ctx: AppContext) {
|
|
20
20
|
const getMatches = createPipeline(skeleton, hydration, noBlocks, presentation)
|
|
@@ -48,12 +48,13 @@ const skeleton = async (
|
|
|
48
48
|
): Promise<SkeletonState> => {
|
|
49
49
|
const { params, ctx } = input
|
|
50
50
|
const actor = params.hydrateCtx.viewer
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
const { cursor, subjects } = await callRolodexClient(
|
|
52
|
+
ctx.rolodexClient.getMatches({
|
|
53
|
+
actor: params.hydrateCtx.viewer,
|
|
54
|
+
limit: params.limit,
|
|
55
|
+
cursor: params.cursor,
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
57
58
|
return {
|
|
58
59
|
actor,
|
|
59
60
|
subjects,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AppContext } from '../../../../context'
|
|
2
2
|
import { Server } from '../../../../lexicon'
|
|
3
3
|
import { SyncStatus } from '../../../../lexicon/types/app/bsky/contact/defs'
|
|
4
|
-
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
4
|
+
import { assertRolodexOrThrowUnimplemented, callRolodexClient } from './util'
|
|
5
5
|
|
|
6
6
|
export default function (server: Server, ctx: AppContext) {
|
|
7
7
|
server.app.bsky.contact.getSyncStatus({
|
|
@@ -10,10 +10,11 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
10
10
|
assertRolodexOrThrowUnimplemented(ctx)
|
|
11
11
|
|
|
12
12
|
const actor = auth.credentials.iss
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const res = await callRolodexClient(
|
|
14
|
+
ctx.rolodexClient.getSyncStatus({
|
|
15
|
+
actor,
|
|
16
|
+
}),
|
|
17
|
+
)
|
|
17
18
|
|
|
18
19
|
let syncStatus: SyncStatus | undefined
|
|
19
20
|
if (res.status && res.status.syncedAt) {
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import { ImportContactsMatch } from '../../../../proto/rolodex_pb'
|
|
18
18
|
import { RolodexClient } from '../../../../rolodex'
|
|
19
19
|
import { Views } from '../../../../views'
|
|
20
|
-
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
20
|
+
import { assertRolodexOrThrowUnimplemented, callRolodexClient } from './util'
|
|
21
21
|
|
|
22
22
|
export default function (server: Server, ctx: AppContext) {
|
|
23
23
|
const importContacts = createPipeline(
|
|
@@ -56,12 +56,13 @@ const skeleton = async (
|
|
|
56
56
|
): Promise<SkeletonState> => {
|
|
57
57
|
const { params, ctx } = input
|
|
58
58
|
const actor = params.hydrateCtx.viewer
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
const { matches } = await callRolodexClient(
|
|
60
|
+
ctx.rolodexClient.importContacts({
|
|
61
|
+
actor: params.hydrateCtx.viewer,
|
|
62
|
+
contacts: params.contacts,
|
|
63
|
+
token: params.token,
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
65
66
|
return {
|
|
66
67
|
actor,
|
|
67
68
|
matches,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppContext } from '../../../../context'
|
|
2
2
|
import { Server } from '../../../../lexicon'
|
|
3
|
-
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
3
|
+
import { assertRolodexOrThrowUnimplemented, callRolodexClient } from './util'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
6
|
server.app.bsky.contact.removeData({
|
|
@@ -9,10 +9,11 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
9
9
|
assertRolodexOrThrowUnimplemented(ctx)
|
|
10
10
|
|
|
11
11
|
const actor = auth.credentials.iss
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
await callRolodexClient(
|
|
13
|
+
ctx.rolodexClient.removeData({
|
|
14
|
+
actor,
|
|
15
|
+
}),
|
|
16
|
+
)
|
|
16
17
|
|
|
17
18
|
return {
|
|
18
19
|
encoding: 'application/json',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TID } from '@atproto/common'
|
|
2
2
|
import { AppContext } from '../../../../context'
|
|
3
3
|
import { Server } from '../../../../lexicon'
|
|
4
|
+
import { Notification } from '../../../../lexicon/types/app/bsky/contact/defs'
|
|
4
5
|
import { Namespaces } from '../../../../stash'
|
|
5
6
|
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
6
7
|
|
|
@@ -19,7 +20,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
19
20
|
payload: {
|
|
20
21
|
from,
|
|
21
22
|
to,
|
|
22
|
-
},
|
|
23
|
+
} satisfies Notification,
|
|
23
24
|
key: TID.nextStr(),
|
|
24
25
|
})
|
|
25
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppContext } from '../../../../context'
|
|
2
2
|
import { Server } from '../../../../lexicon'
|
|
3
|
-
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
3
|
+
import { assertRolodexOrThrowUnimplemented, callRolodexClient } from './util'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
6
|
server.app.bsky.contact.startPhoneVerification({
|
|
@@ -9,11 +9,12 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
9
9
|
assertRolodexOrThrowUnimplemented(ctx)
|
|
10
10
|
|
|
11
11
|
const actor = auth.credentials.iss
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
await callRolodexClient(
|
|
13
|
+
ctx.rolodexClient.startPhoneVerification({
|
|
14
|
+
actor,
|
|
15
|
+
phone: input.body.phone,
|
|
16
|
+
}),
|
|
17
|
+
)
|
|
17
18
|
|
|
18
19
|
return {
|
|
19
20
|
encoding: 'application/json',
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConnectError } from '@connectrpc/connect'
|
|
2
|
+
import {
|
|
3
|
+
InternalServerError,
|
|
4
|
+
InvalidRequestError,
|
|
5
|
+
MethodNotImplementedError,
|
|
6
|
+
} from '@atproto/xrpc-server'
|
|
2
7
|
import { AppContext } from '../../../..'
|
|
3
8
|
import { RolodexClient } from '../../../../rolodex'
|
|
4
9
|
|
|
@@ -11,3 +16,77 @@ export function assertRolodexOrThrowUnimplemented(
|
|
|
11
16
|
)
|
|
12
17
|
}
|
|
13
18
|
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Converts UPPERCASE_ERROR from Rolodex to PascalCase for XRPC.
|
|
22
|
+
*/
|
|
23
|
+
function convertErrorName(reason: string): string {
|
|
24
|
+
switch (reason) {
|
|
25
|
+
case 'INVALID_DID':
|
|
26
|
+
return 'InvalidDid'
|
|
27
|
+
case 'INVALID_LIMIT':
|
|
28
|
+
return 'InvalidLimit'
|
|
29
|
+
case 'INVALID_CURSOR':
|
|
30
|
+
return 'InvalidCursor'
|
|
31
|
+
case 'INVALID_CONTACTS':
|
|
32
|
+
return 'InvalidContacts'
|
|
33
|
+
case 'TOO_MANY_CONTACTS':
|
|
34
|
+
return 'TooManyContacts'
|
|
35
|
+
case 'INVALID_TOKEN':
|
|
36
|
+
return 'InvalidToken'
|
|
37
|
+
case 'RATE_LIMIT_EXCEEDED':
|
|
38
|
+
return 'RateLimitExceeded'
|
|
39
|
+
case 'INVALID_PHONE':
|
|
40
|
+
return 'InvalidPhone'
|
|
41
|
+
case 'INVALID_CODE':
|
|
42
|
+
return 'InvalidCode'
|
|
43
|
+
case 'INTERNAL_ERROR':
|
|
44
|
+
return 'InternalError'
|
|
45
|
+
default:
|
|
46
|
+
return reason
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Helper to call Rolodex client methods and translate RPC errors to XRPC
|
|
52
|
+
* errors.
|
|
53
|
+
*
|
|
54
|
+
* These `reason` values need to stay in sync with the Rolodex service
|
|
55
|
+
*/
|
|
56
|
+
export async function callRolodexClient<T>(caller: T) {
|
|
57
|
+
try {
|
|
58
|
+
return await caller
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// might be something we want to handle
|
|
61
|
+
if (e instanceof ConnectError) {
|
|
62
|
+
/**
|
|
63
|
+
* https://connectrpc.com/docs/protocol#error-end-stream
|
|
64
|
+
*/
|
|
65
|
+
const details = e.details?.at(0) as
|
|
66
|
+
| {
|
|
67
|
+
debug: {
|
|
68
|
+
reason: string
|
|
69
|
+
message: string
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
| undefined
|
|
73
|
+
const reason = details?.debug?.reason // e.g. INVALID_DID
|
|
74
|
+
// Handle known error reasons
|
|
75
|
+
if (reason) {
|
|
76
|
+
const errorName = convertErrorName(reason)
|
|
77
|
+
// NOTE: Don't leak e.message to the response.
|
|
78
|
+
|
|
79
|
+
if (reason === 'INTERNAL_ERROR') {
|
|
80
|
+
throw new InternalServerError('Upstream error', errorName, {
|
|
81
|
+
cause: e,
|
|
82
|
+
})
|
|
83
|
+
} else {
|
|
84
|
+
throw new InvalidRequestError('An error occurred', errorName, {
|
|
85
|
+
cause: e,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw e
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppContext } from '../../../../context'
|
|
2
2
|
import { Server } from '../../../../lexicon'
|
|
3
|
-
import { assertRolodexOrThrowUnimplemented } from './util'
|
|
3
|
+
import { assertRolodexOrThrowUnimplemented, callRolodexClient } from './util'
|
|
4
4
|
|
|
5
5
|
export default function (server: Server, ctx: AppContext) {
|
|
6
6
|
server.app.bsky.contact.verifyPhone({
|
|
@@ -9,12 +9,13 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
9
9
|
assertRolodexOrThrowUnimplemented(ctx)
|
|
10
10
|
|
|
11
11
|
const actor = auth.credentials.iss
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
const res = await callRolodexClient(
|
|
13
|
+
ctx.rolodexClient.verifyPhone({
|
|
14
|
+
actor,
|
|
15
|
+
verificationCode: input.body.code,
|
|
16
|
+
phone: input.body.phone,
|
|
17
|
+
}),
|
|
18
|
+
)
|
|
18
19
|
|
|
19
20
|
return {
|
|
20
21
|
encoding: 'application/json',
|
package/src/api/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import getMatches from './app/bsky/contact/getMatches'
|
|
|
16
16
|
import getSyncStatus from './app/bsky/contact/getSyncStatus'
|
|
17
17
|
import importContacts from './app/bsky/contact/importContacts'
|
|
18
18
|
import removeData from './app/bsky/contact/removeData'
|
|
19
|
+
import sendNotification from './app/bsky/contact/sendNotification'
|
|
19
20
|
import startPhoneVerification from './app/bsky/contact/startPhoneVerification'
|
|
20
21
|
import verifyPhone from './app/bsky/contact/verifyPhone'
|
|
21
22
|
import getActorFeeds from './app/bsky/feed/getActorFeeds'
|
|
@@ -95,6 +96,8 @@ export * as blobResolver from './blob-resolver'
|
|
|
95
96
|
|
|
96
97
|
export * as external from './external'
|
|
97
98
|
|
|
99
|
+
export * as sitemap from './sitemap'
|
|
100
|
+
|
|
98
101
|
export default function (server: Server, ctx: AppContext) {
|
|
99
102
|
// app.bsky
|
|
100
103
|
getTimeline(server, ctx)
|
|
@@ -106,6 +109,7 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
106
109
|
getSyncStatus(server, ctx)
|
|
107
110
|
importContacts(server, ctx)
|
|
108
111
|
removeData(server, ctx)
|
|
112
|
+
sendNotification(server, ctx)
|
|
109
113
|
startPhoneVerification(server, ctx)
|
|
110
114
|
verifyPhone(server, ctx)
|
|
111
115
|
getActorFeeds(server, ctx)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Readable } from 'node:stream'
|
|
2
|
+
import { Timestamp } from '@bufbuild/protobuf'
|
|
3
|
+
import { Code, ConnectError } from '@connectrpc/connect'
|
|
4
|
+
import express, { RequestHandler, Router } from 'express'
|
|
5
|
+
import { AppContext } from '../context'
|
|
6
|
+
import { httpLogger as log } from '../logger'
|
|
7
|
+
import { SitemapPageType } from '../proto/bsky_pb'
|
|
8
|
+
|
|
9
|
+
export const createRouter = (ctx: AppContext): Router => {
|
|
10
|
+
const router = Router()
|
|
11
|
+
router.get('/external/sitemap/users.xml.gz', userIndexHandler(ctx))
|
|
12
|
+
router.get(
|
|
13
|
+
'/external/sitemap/users/:date/:bucket.xml.gz',
|
|
14
|
+
userPageHandler(ctx),
|
|
15
|
+
)
|
|
16
|
+
return router
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const userIndexHandler =
|
|
20
|
+
(ctx: AppContext): RequestHandler =>
|
|
21
|
+
async (_req: express.Request, res: express.Response) => {
|
|
22
|
+
try {
|
|
23
|
+
const result = await ctx.dataplane.getSitemapIndex({
|
|
24
|
+
type: SitemapPageType.USER,
|
|
25
|
+
})
|
|
26
|
+
res.set('Content-Type', 'application/gzip')
|
|
27
|
+
res.set('Content-Encoding', 'gzip')
|
|
28
|
+
Readable.from(Buffer.from(result.sitemap)).pipe(res)
|
|
29
|
+
} catch (err) {
|
|
30
|
+
log.error({ err }, 'failed to get sitemap index')
|
|
31
|
+
return res.status(500).send('Internal Server Error')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const userPageHandler =
|
|
36
|
+
(ctx: AppContext): RequestHandler =>
|
|
37
|
+
async (req: express.Request, res: express.Response) => {
|
|
38
|
+
const { date, bucket } = req.params
|
|
39
|
+
|
|
40
|
+
// Parse date (YYYY-MM-DD format)
|
|
41
|
+
const dateParts = date.split('-')
|
|
42
|
+
if (dateParts.length !== 3) {
|
|
43
|
+
return res.status(400).send('Invalid date format. Expected YYYY-MM-DD')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const year = parseInt(dateParts[0], 10)
|
|
47
|
+
const month = parseInt(dateParts[1], 10)
|
|
48
|
+
const day = parseInt(dateParts[2], 10)
|
|
49
|
+
|
|
50
|
+
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
|
51
|
+
return res.status(400).send('Invalid date format. Expected YYYY-MM-DD')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Parse bucket (1-indexed)
|
|
55
|
+
const bucketNum = parseInt(bucket, 10)
|
|
56
|
+
if (isNaN(bucketNum) || bucketNum < 1) {
|
|
57
|
+
return res.status(400).send('Invalid bucket number')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = await ctx.dataplane.getSitemapPage({
|
|
62
|
+
type: SitemapPageType.USER,
|
|
63
|
+
date: Timestamp.fromDate(new Date(year, month - 1, day)),
|
|
64
|
+
bucket: bucketNum,
|
|
65
|
+
})
|
|
66
|
+
res.set('Content-Type', 'application/gzip')
|
|
67
|
+
res.set('Content-Encoding', 'gzip')
|
|
68
|
+
Readable.from(Buffer.from(result.sitemap)).pipe(res)
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof ConnectError && err.code === Code.NotFound) {
|
|
71
|
+
return res.status(404).send('Sitemap page not found')
|
|
72
|
+
}
|
|
73
|
+
log.error({ err }, 'failed to get sitemap page')
|
|
74
|
+
return res.status(500).send('Internal Server Error')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -22,6 +22,7 @@ import records from './records'
|
|
|
22
22
|
import relationships from './relationships'
|
|
23
23
|
import reposts from './reposts'
|
|
24
24
|
import search from './search'
|
|
25
|
+
import sitemap from './sitemap'
|
|
25
26
|
import starterPacks from './starter-packs'
|
|
26
27
|
import suggestions from './suggestions'
|
|
27
28
|
import sync from './sync'
|
|
@@ -50,6 +51,7 @@ export default (db: Database, idResolver: IdResolver) =>
|
|
|
50
51
|
...relationships(db),
|
|
51
52
|
...reposts(db),
|
|
52
53
|
...search(db),
|
|
54
|
+
...sitemap(),
|
|
53
55
|
...suggestions(db),
|
|
54
56
|
...sync(db),
|
|
55
57
|
...threads(db),
|
|
@@ -136,12 +136,14 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
136
136
|
|
|
137
137
|
const status = row?.ageAssuranceStatus ?? 'unknown'
|
|
138
138
|
let access = row?.ageAssuranceAccess
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
if (!access || access === 'unknown') {
|
|
140
|
+
if (status === 'assured') {
|
|
141
|
+
access = 'full'
|
|
142
|
+
} else if (status === 'blocked') {
|
|
143
|
+
access = 'none'
|
|
144
|
+
} else {
|
|
145
|
+
access = 'unknown'
|
|
146
|
+
}
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
return {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { gzipSync } from 'node:zlib'
|
|
2
|
+
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
3
|
+
import { Service } from '../../../proto/bsky_connect'
|
|
4
|
+
import { GetSitemapPageRequest } from '../../../proto/bsky_pb'
|
|
5
|
+
|
|
6
|
+
const MOCK_SITEMAP_INDEX = `<?xml version="1.0" encoding="UTF-8"?>
|
|
7
|
+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
8
|
+
<sitemap>
|
|
9
|
+
<loc>https://bsky.app/sitemap/users/2025-01-01/1.xml.gz</loc>
|
|
10
|
+
</sitemap>
|
|
11
|
+
</sitemapindex>`
|
|
12
|
+
|
|
13
|
+
const MOCK_SITEMAP_PAGE = `<?xml version="1.0" encoding="UTF-8"?>
|
|
14
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
15
|
+
<url>
|
|
16
|
+
<loc>https://bsky.app/profile/test.bsky.social</loc>
|
|
17
|
+
</url>
|
|
18
|
+
</urlset>`
|
|
19
|
+
|
|
20
|
+
export default (): Partial<ServiceImpl<typeof Service>> => ({
|
|
21
|
+
async getSitemapIndex() {
|
|
22
|
+
return {
|
|
23
|
+
sitemap: gzipSync(Buffer.from(MOCK_SITEMAP_INDEX)),
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
async getSitemapPage(req: GetSitemapPageRequest) {
|
|
27
|
+
const date = req.date?.toDate()
|
|
28
|
+
const isExpectedDate =
|
|
29
|
+
date &&
|
|
30
|
+
date.getFullYear() === 2025 &&
|
|
31
|
+
date.getMonth() === 0 &&
|
|
32
|
+
date.getDate() === 1
|
|
33
|
+
const isExpectedBucket = req.bucket === 1
|
|
34
|
+
|
|
35
|
+
if (!isExpectedDate || !isExpectedBucket) {
|
|
36
|
+
throw new ConnectError('Sitemap page not found', Code.NotFound)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
sitemap: gzipSync(Buffer.from(MOCK_SITEMAP_PAGE)),
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
})
|
package/src/hydration/actor.ts
CHANGED
|
@@ -273,7 +273,7 @@ export class ActorHydrator {
|
|
|
273
273
|
includeTakedowns = false,
|
|
274
274
|
): Promise<NotificationDeclarations> {
|
|
275
275
|
if (!uris.length) return new HydrationMap<NotificationDeclaration>()
|
|
276
|
-
const res = await this.dataplane.
|
|
276
|
+
const res = await this.dataplane.getNotificationDeclarationRecords({ uris })
|
|
277
277
|
return uris.reduce((acc, uri, i) => {
|
|
278
278
|
const record = parseRecord<NotificationDeclarationRecord>(
|
|
279
279
|
res.records[i],
|