@atproto/ozone 0.2.8 → 0.2.11
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 +33 -0
- package/package.json +25 -21
- package/bin/migration-create.ts +0 -38
- package/jest.config.cjs +0 -22
- package/src/api/chat/getActorMetadata.ts +0 -23
- package/src/api/chat/getConvo.ts +0 -23
- package/src/api/chat/getConvoMembers.ts +0 -23
- package/src/api/chat/getConvos.ts +0 -23
- package/src/api/chat/getMessageContext.ts +0 -42
- package/src/api/chat/index.ts +0 -16
- package/src/api/communication/createTemplate.ts +0 -51
- package/src/api/communication/deleteTemplate.ts +0 -23
- package/src/api/communication/listTemplates.ts +0 -31
- package/src/api/communication/updateTemplate.ts +0 -51
- package/src/api/health.ts +0 -27
- package/src/api/index.ts +0 -146
- package/src/api/label/fetchLabels.ts +0 -32
- package/src/api/label/queryLabels.ts +0 -57
- package/src/api/label/subscribeLabels.ts +0 -25
- package/src/api/moderation/cancelScheduledActions.ts +0 -72
- package/src/api/moderation/emitEvent.ts +0 -475
- package/src/api/moderation/getAccountTimeline.ts +0 -160
- package/src/api/moderation/getEvent.ts +0 -19
- package/src/api/moderation/getRecord.ts +0 -40
- package/src/api/moderation/getRecords.ts +0 -50
- package/src/api/moderation/getRepo.ts +0 -34
- package/src/api/moderation/getReporterStats.ts +0 -18
- package/src/api/moderation/getRepos.ts +0 -41
- package/src/api/moderation/getSubjects.ts +0 -101
- package/src/api/moderation/listScheduledActions.ts +0 -45
- package/src/api/moderation/queryEvents.ts +0 -72
- package/src/api/moderation/queryStatuses.ts +0 -23
- package/src/api/moderation/scheduleAction.ts +0 -129
- package/src/api/moderation/searchRepos.ts +0 -46
- package/src/api/moderation/util.ts +0 -96
- package/src/api/proxied.ts +0 -327
- package/src/api/queue/assignModerator.ts +0 -31
- package/src/api/queue/createQueue.ts +0 -62
- package/src/api/queue/deleteQueue.ts +0 -56
- package/src/api/queue/getAssignments.ts +0 -19
- package/src/api/queue/listQueues.ts +0 -39
- package/src/api/queue/routeReports.ts +0 -44
- package/src/api/queue/unassignModerator.ts +0 -26
- package/src/api/queue/updateQueue.ts +0 -54
- package/src/api/report/assignModerator.ts +0 -36
- package/src/api/report/createActivity.ts +0 -57
- package/src/api/report/createReport.ts +0 -93
- package/src/api/report/getAssignments.ts +0 -20
- package/src/api/report/getHistoricalStats.ts +0 -41
- package/src/api/report/getLatestReport.ts +0 -44
- package/src/api/report/getLiveStats.ts +0 -26
- package/src/api/report/getReport.ts +0 -55
- package/src/api/report/listActivities.ts +0 -37
- package/src/api/report/queryActivities.ts +0 -64
- package/src/api/report/queryReports.ts +0 -44
- package/src/api/report/reassignQueue.ts +0 -68
- package/src/api/report/refreshStats.ts +0 -27
- package/src/api/report/unassignModerator.ts +0 -21
- package/src/api/safelink/addRule.ts +0 -48
- package/src/api/safelink/queryEvents.ts +0 -32
- package/src/api/safelink/queryRules.ts +0 -58
- package/src/api/safelink/removeRule.ts +0 -42
- package/src/api/safelink/updateRule.ts +0 -48
- package/src/api/server/getConfig.ts +0 -35
- package/src/api/set/addValues.ts +0 -28
- package/src/api/set/deleteSet.ts +0 -34
- package/src/api/set/deleteValues.ts +0 -31
- package/src/api/set/getValues.ts +0 -42
- package/src/api/set/querySets.ts +0 -36
- package/src/api/set/upsertSet.ts +0 -38
- package/src/api/setting/listOptions.ts +0 -44
- package/src/api/setting/removeOptions.ts +0 -64
- package/src/api/setting/upsertOption.ts +0 -156
- package/src/api/team/addMember.ts +0 -51
- package/src/api/team/deleteMember.ts +0 -29
- package/src/api/team/listMembers.ts +0 -20
- package/src/api/team/updateMember.ts +0 -47
- package/src/api/util.ts +0 -265
- package/src/api/verification/grantVerifications.ts +0 -90
- package/src/api/verification/listVerifications.ts +0 -44
- package/src/api/verification/revokeVerifications.ts +0 -43
- package/src/api/well-known.ts +0 -46
- package/src/assignment/index.ts +0 -728
- package/src/auth-verifier.ts +0 -227
- package/src/background.ts +0 -183
- package/src/communication-service/template.ts +0 -110
- package/src/communication-service/util.ts +0 -8
- package/src/config/config.ts +0 -211
- package/src/config/env.ts +0 -95
- package/src/config/index.ts +0 -3
- package/src/config/secrets.ts +0 -17
- package/src/context.ts +0 -399
- package/src/daemon/blob-diverter.ts +0 -186
- package/src/daemon/context.ts +0 -247
- package/src/daemon/event-pusher.ts +0 -363
- package/src/daemon/event-reverser.ts +0 -128
- package/src/daemon/index.ts +0 -33
- package/src/daemon/job-cursor.ts +0 -33
- package/src/daemon/materialized-view-refresher.ts +0 -33
- package/src/daemon/queue-router.ts +0 -101
- package/src/daemon/scheduled-action-processor.ts +0 -304
- package/src/daemon/stats-computer.ts +0 -101
- package/src/daemon/strike-expiry-processor.ts +0 -95
- package/src/daemon/team-profile-synchronizer.ts +0 -15
- package/src/daemon/verification-listener.ts +0 -169
- package/src/db/index.ts +0 -203
- package/src/db/migrations/20231219T205730722Z-init.ts +0 -170
- package/src/db/migrations/20240116T085607200Z-communication-template.ts +0 -23
- package/src/db/migrations/20240201T051104136Z-mod-event-blobs.ts +0 -15
- package/src/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.ts +0 -31
- package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +0 -25
- package/src/db/migrations/20240408T192432676Z-mute-reporting.ts +0 -15
- package/src/db/migrations/20240506T225055595Z-message-subject.ts +0 -21
- package/src/db/migrations/20240521T211332580Z-member.ts +0 -17
- package/src/db/migrations/20240814T003647759Z-event-created-at-index.ts +0 -13
- package/src/db/migrations/20240903T205730722Z-add-template-lang.ts +0 -12
- package/src/db/migrations/20240904T205730722Z-add-subject-did-index.ts +0 -13
- package/src/db/migrations/20241001T205730722Z-subject-status-review-state-index.ts +0 -15
- package/src/db/migrations/20241008T205730722Z-sets.ts +0 -53
- package/src/db/migrations/20241018T205730722Z-setting.ts +0 -27
- package/src/db/migrations/20241026T205730722Z-add-hosting-status-to-subject-status.ts +0 -57
- package/src/db/migrations/20241220T144630860Z-stats-materialized-views.ts +0 -215
- package/src/db/migrations/20250204T003647759Z-add-subject-priority-score.ts +0 -22
- package/src/db/migrations/20250211T003647759Z-add-reporter-stats-index.ts +0 -38
- package/src/db/migrations/20250211T132135150Z-moderation-event-message-partial-idx.ts +0 -26
- package/src/db/migrations/20250221T132135150Z-member-details.ts +0 -14
- package/src/db/migrations/20250404T201720309Z-subject-status-sort-idxs.ts +0 -18
- package/src/db/migrations/20250415T201720309Z-verification.ts +0 -34
- package/src/db/migrations/20250417T201720309Z-firehose-cursor.ts +0 -16
- package/src/db/migrations/20250609T110704000Z-safelink.ts +0 -53
- package/src/db/migrations/20250618T180246000Z-add-mod-tool-to-moderation-event.ts +0 -18
- package/src/db/migrations/20250701T000000000Z-add-age-assurance-state.ts +0 -25
- package/src/db/migrations/20250715T000000000Z-add-mod-event-external-id.ts +0 -15
- package/src/db/migrations/20250718T150931000Z-update-appeal-reason-stats.ts +0 -310
- package/src/db/migrations/20250813T000000000Z-mod-tool-batch-id-index.ts +0 -14
- package/src/db/migrations/20250923T000000000Z-scheduled-actions.ts +0 -56
- package/src/db/migrations/20251008T120000000Z-add-strike-system.ts +0 -87
- package/src/db/migrations/20260210T154806448Z-mod-event-created-by-indexes.ts +0 -22
- package/src/db/migrations/20260219T164523000Z-create-report-table.ts +0 -155
- package/src/db/migrations/20260219T165302248Z-moderator-assignment.ts +0 -42
- package/src/db/migrations/20260225T000000000Z-add-report-queue-table.ts +0 -41
- package/src/db/migrations/20260313T000000000Z-add-report-activity-table.ts +0 -48
- package/src/db/migrations/20260318T152058935Z-add-report-stat.ts +0 -35
- package/src/db/migrations/20260428T000000000Z-add-expiring-tag-table.ts +0 -32
- package/src/db/migrations/20260513T202941104Z-add-subject-convo-id.ts +0 -114
- package/src/db/migrations/20260602T120000000Z-add-report-activity-created-index.ts +0 -17
- package/src/db/migrations/index.ts +0 -44
- package/src/db/migrations/provider.ts +0 -26
- package/src/db/pagination.ts +0 -335
- package/src/db/schema/account_events_stats.ts +0 -16
- package/src/db/schema/account_record_events_stats.ts +0 -15
- package/src/db/schema/account_record_status_stats.ts +0 -15
- package/src/db/schema/account_strike.ts +0 -13
- package/src/db/schema/blob_push_event.ts +0 -21
- package/src/db/schema/communication_template.ts +0 -19
- package/src/db/schema/expiring_tag.ts +0 -18
- package/src/db/schema/firehose_cursor.ts +0 -13
- package/src/db/schema/index.ts +0 -60
- package/src/db/schema/job_cursor.ts +0 -13
- package/src/db/schema/label.ts +0 -22
- package/src/db/schema/member.ts +0 -22
- package/src/db/schema/moderation_event.ts +0 -61
- package/src/db/schema/moderation_subject_status.ts +0 -52
- package/src/db/schema/moderator_assignment.ts +0 -16
- package/src/db/schema/ozone_set.ts +0 -24
- package/src/db/schema/record_events_stats.ts +0 -15
- package/src/db/schema/record_push_event.ts +0 -21
- package/src/db/schema/repo_push_event.ts +0 -19
- package/src/db/schema/report.ts +0 -28
- package/src/db/schema/report_activity.ts +0 -22
- package/src/db/schema/report_queue.ts +0 -21
- package/src/db/schema/report_stat.ts +0 -27
- package/src/db/schema/safelink.ts +0 -39
- package/src/db/schema/scheduled-action.ts +0 -25
- package/src/db/schema/setting.ts +0 -24
- package/src/db/schema/signing_key.ts +0 -10
- package/src/db/schema/verification.ts +0 -21
- package/src/db/types.ts +0 -24
- package/src/error.ts +0 -12
- package/src/image-invalidator.ts +0 -7
- package/src/index.ts +0 -154
- package/src/jetstream/service.ts +0 -107
- package/src/logger.ts +0 -29
- package/src/mod-service/expiring-tags.ts +0 -104
- package/src/mod-service/index.ts +0 -1842
- package/src/mod-service/profile.ts +0 -139
- package/src/mod-service/report.ts +0 -429
- package/src/mod-service/status.ts +0 -549
- package/src/mod-service/strike.ts +0 -96
- package/src/mod-service/subject.ts +0 -311
- package/src/mod-service/types.ts +0 -96
- package/src/mod-service/util.ts +0 -99
- package/src/mod-service/views.ts +0 -912
- package/src/queue/service.ts +0 -603
- package/src/report/activity.ts +0 -281
- package/src/report/handle-report-update.ts +0 -209
- package/src/report/reassign.ts +0 -109
- package/src/report/stats.ts +0 -852
- package/src/report/views.ts +0 -239
- package/src/safelink/service.ts +0 -304
- package/src/scheduled-action/service.ts +0 -281
- package/src/scheduled-action/types.ts +0 -17
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/outbox.ts +0 -123
- package/src/sequencer/sequencer.ts +0 -147
- package/src/set/service.ts +0 -230
- package/src/setting/constants.ts +0 -3
- package/src/setting/service.ts +0 -148
- package/src/setting/types.ts +0 -3
- package/src/setting/validators.ts +0 -333
- package/src/tag-service/content-tagger.ts +0 -30
- package/src/tag-service/embed-tagger.ts +0 -70
- package/src/tag-service/index.ts +0 -70
- package/src/tag-service/language-data.ts +0 -561
- package/src/tag-service/language-tagger.ts +0 -101
- package/src/tag-service/util.ts +0 -13
- package/src/team/index.ts +0 -296
- package/src/util.ts +0 -230
- package/src/verification/issuer.ts +0 -146
- package/src/verification/service.ts +0 -208
- package/src/verification/util.ts +0 -53
- package/test.env +0 -2
- package/tests/3p-labeler.test.ts +0 -288
- package/tests/__snapshots__/account-strikes.test.ts.snap +0 -159
- package/tests/__snapshots__/age-assurance.test.ts.snap +0 -66
- package/tests/__snapshots__/blob-divert.test.ts.snap +0 -219
- package/tests/__snapshots__/get-account-timeline.test.ts.snap +0 -36
- package/tests/__snapshots__/get-record.test.ts.snap +0 -271
- package/tests/__snapshots__/get-records.test.ts.snap +0 -175
- package/tests/__snapshots__/get-repo.test.ts.snap +0 -91
- package/tests/__snapshots__/get-repos.test.ts.snap +0 -127
- package/tests/__snapshots__/get-starter-pack.test.ts.snap +0 -535
- package/tests/__snapshots__/get-subjects.test.ts.snap +0 -529
- package/tests/__snapshots__/moderation-events.test.ts.snap +0 -347
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +0 -276
- package/tests/__snapshots__/moderation.test.ts.snap +0 -85
- package/tests/__snapshots__/report-reason.test.ts.snap +0 -14
- package/tests/__snapshots__/safelink.test.ts.snap +0 -179
- package/tests/__snapshots__/scheduled-action.test.ts.snap +0 -61
- package/tests/__snapshots__/sets.test.ts.snap +0 -46
- package/tests/__snapshots__/settings.test.ts.snap +0 -52
- package/tests/__snapshots__/team.test.ts.snap +0 -374
- package/tests/__snapshots__/verification-listener.test.ts.snap +0 -152
- package/tests/__snapshots__/verification.test.ts.snap +0 -302
- package/tests/_util.ts +0 -242
- package/tests/account-strikes.test.ts +0 -184
- package/tests/ack-all-subjects-of-account.test.ts +0 -177
- package/tests/age-assurance.test.ts +0 -372
- package/tests/blob-divert.test.ts +0 -106
- package/tests/communication-templates.test.ts +0 -149
- package/tests/content-tagger.test.ts +0 -170
- package/tests/db.test.ts +0 -184
- package/tests/expiring-label.test.ts +0 -72
- package/tests/expiring-tags.test.ts +0 -232
- package/tests/get-account-timeline.test.ts +0 -85
- package/tests/get-config.test.ts +0 -55
- package/tests/get-lists.test.ts +0 -111
- package/tests/get-profiles.test.ts +0 -70
- package/tests/get-record.test.ts +0 -130
- package/tests/get-records.test.ts +0 -91
- package/tests/get-repo.test.ts +0 -171
- package/tests/get-report.test.ts +0 -136
- package/tests/get-reporter-stats.test.ts +0 -132
- package/tests/get-repos.test.ts +0 -91
- package/tests/get-starter-pack.test.ts +0 -115
- package/tests/get-subjects.test.ts +0 -81
- package/tests/mod-tool.test.ts +0 -268
- package/tests/moderation-appeals.test.ts +0 -260
- package/tests/moderation-events.test.ts +0 -756
- package/tests/moderation-status-tags.test.ts +0 -140
- package/tests/moderation-statuses.test.ts +0 -495
- package/tests/moderation.test.ts +0 -992
- package/tests/protected-tags.test.ts +0 -218
- package/tests/query-labels.test.ts +0 -238
- package/tests/query-reports.test.ts +0 -608
- package/tests/queue-assignment.test.ts +0 -428
- package/tests/queue-router.test.ts +0 -306
- package/tests/queues.test.ts +0 -690
- package/tests/record-and-account-events.test.ts +0 -197
- package/tests/repo-search.test.ts +0 -136
- package/tests/report-action.test.ts +0 -308
- package/tests/report-activity.test.ts +0 -711
- package/tests/report-assignment.test.ts +0 -517
- package/tests/report-muting.test.ts +0 -100
- package/tests/report-reason.test.ts +0 -154
- package/tests/report-reassign-queue.test.ts +0 -340
- package/tests/report-routing.test.ts +0 -245
- package/tests/report-stats.test.ts +0 -545
- package/tests/revoke-account-credentials.test.ts +0 -54
- package/tests/safelink.test.ts +0 -534
- package/tests/scheduled-action-processor.test.ts +0 -488
- package/tests/scheduled-action.test.ts +0 -334
- package/tests/sequencer.test.ts +0 -227
- package/tests/server.test.ts +0 -62
- package/tests/sets.test.ts +0 -246
- package/tests/settings.test.ts +0 -308
- package/tests/strike-expiry-processor.test.ts +0 -299
- package/tests/subject-priority-score.test.ts +0 -96
- package/tests/takedown.test.ts +0 -105
- package/tests/team.test.ts +0 -216
- package/tests/verification-listener.test.ts +0 -129
- package/tests/verification.test.ts +0 -186
- package/tsconfig.build.json +0 -9
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -8
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import lande from 'lande'
|
|
2
|
-
import {
|
|
3
|
-
AppBskyActorProfile,
|
|
4
|
-
AppBskyFeedGenerator,
|
|
5
|
-
AppBskyFeedPost,
|
|
6
|
-
AppBskyGraphList,
|
|
7
|
-
} from '@atproto/api'
|
|
8
|
-
import { langLogger as log } from '../logger.js'
|
|
9
|
-
import { ContentTagger } from './content-tagger.js'
|
|
10
|
-
import { code3ToCode2 } from './language-data.js'
|
|
11
|
-
|
|
12
|
-
const ifString = (value: unknown): string | undefined =>
|
|
13
|
-
typeof value === 'string' ? value : undefined
|
|
14
|
-
const isStringProp = (obj: object, prop: string): string | undefined =>
|
|
15
|
-
prop in obj ? ifString(obj[prop]) : undefined
|
|
16
|
-
|
|
17
|
-
export class LanguageTagger extends ContentTagger {
|
|
18
|
-
tagPrefix = 'lang:'
|
|
19
|
-
|
|
20
|
-
isApplicable(): boolean {
|
|
21
|
-
return !!this.subjectStatus && !this.tagAlreadyExists()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async buildTags(): Promise<string[]> {
|
|
25
|
-
try {
|
|
26
|
-
const recordLangs = await this.getRecordLang()
|
|
27
|
-
return recordLangs
|
|
28
|
-
? recordLangs.map((lang) => `${this.tagPrefix}${lang}`)
|
|
29
|
-
: [`${this.tagPrefix}und`]
|
|
30
|
-
} catch (err) {
|
|
31
|
-
log.error({ subject: this.subject, err }, 'Error getting record langs')
|
|
32
|
-
return []
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getTextFromRecord(recordValue: Record<string, unknown>): string | undefined {
|
|
37
|
-
let text: string | undefined
|
|
38
|
-
|
|
39
|
-
if (AppBskyGraphList.isRecord(recordValue)) {
|
|
40
|
-
text =
|
|
41
|
-
isStringProp(recordValue, 'description') ||
|
|
42
|
-
isStringProp(recordValue, 'name')
|
|
43
|
-
} else if (
|
|
44
|
-
AppBskyFeedGenerator.isRecord(recordValue) ||
|
|
45
|
-
AppBskyActorProfile.isRecord(recordValue)
|
|
46
|
-
) {
|
|
47
|
-
text =
|
|
48
|
-
isStringProp(recordValue, 'description') ||
|
|
49
|
-
isStringProp(recordValue, 'displayName')
|
|
50
|
-
} else if (AppBskyFeedPost.isRecord(recordValue)) {
|
|
51
|
-
text = isStringProp(recordValue, 'text')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return text?.trim()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async getRecordLang(): Promise<string[] | null> {
|
|
58
|
-
const langs = new Set<string>()
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
this.subject.isRepo() ||
|
|
62
|
-
(this.subject.isRecord() &&
|
|
63
|
-
this.subject.uri.endsWith('/app.bsky.actor.profile/self'))
|
|
64
|
-
) {
|
|
65
|
-
const feed = await this.moderationService.views.fetchAuthorFeed(
|
|
66
|
-
this.subject.did,
|
|
67
|
-
)
|
|
68
|
-
feed.forEach((item) => {
|
|
69
|
-
const itemLangs = item.post.record['langs'] as string[] | null
|
|
70
|
-
if (itemLangs?.length) {
|
|
71
|
-
// Pick the first fragment of the lang code so that instead of `en-US` and `en-GB` we get `en`
|
|
72
|
-
itemLangs.forEach((lang) => langs.add(lang.split('-')[0]))
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (this.subject.isRecord()) {
|
|
78
|
-
const recordByUri = await this.moderationService.views.fetchRecords([
|
|
79
|
-
this.subject,
|
|
80
|
-
])
|
|
81
|
-
const record = recordByUri.get(this.subject.uri)
|
|
82
|
-
const recordLang = record?.value.langs as string[] | null
|
|
83
|
-
const recordText = record
|
|
84
|
-
? this.getTextFromRecord(record.value)
|
|
85
|
-
: undefined
|
|
86
|
-
if (recordLang?.length) {
|
|
87
|
-
recordLang
|
|
88
|
-
.map((lang) => lang.split('-')[0])
|
|
89
|
-
.forEach((lang) => langs.add(lang))
|
|
90
|
-
} else if (recordText) {
|
|
91
|
-
const detectedLanguages = lande(recordText)
|
|
92
|
-
if (detectedLanguages.length) {
|
|
93
|
-
const langCode = code3ToCode2(detectedLanguages[0][0])
|
|
94
|
-
if (langCode) langs.add(langCode)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return langs.size > 0 ? Array.from(langs) : null
|
|
100
|
-
}
|
|
101
|
-
}
|
package/src/tag-service/util.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ReasonType } from '../lexicon/types/com/atproto/moderation/defs.js'
|
|
2
|
-
|
|
3
|
-
export const getTagForReport = (reasonType: ReasonType) => {
|
|
4
|
-
const reasonWithoutPrefix = reasonType
|
|
5
|
-
.replace('com.atproto.moderation.defs#reason', '')
|
|
6
|
-
.replace('tools.ozone.report.defs#reason', '')
|
|
7
|
-
|
|
8
|
-
const kebabCase = reasonWithoutPrefix
|
|
9
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
10
|
-
.toLowerCase()
|
|
11
|
-
|
|
12
|
-
return `report:${kebabCase}`
|
|
13
|
-
}
|
package/src/team/index.ts
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
import { Selectable } from 'kysely'
|
|
2
|
-
import AtpAgent from '@atproto/api'
|
|
3
|
-
import { chunkArray } from '@atproto/common'
|
|
4
|
-
import { InvalidRequestError } from '@atproto/xrpc-server'
|
|
5
|
-
import { Database } from '../db/index.js'
|
|
6
|
-
import { Member } from '../db/schema/member.js'
|
|
7
|
-
import { ids } from '../lexicon/lexicons.js'
|
|
8
|
-
import { ProfileViewDetailed } from '../lexicon/types/app/bsky/actor/defs.js'
|
|
9
|
-
import { Member as TeamMember } from '../lexicon/types/tools/ozone/team/defs.js'
|
|
10
|
-
import { httpLogger } from '../logger.js'
|
|
11
|
-
import { AuthHeaders } from '../mod-service/views.js'
|
|
12
|
-
|
|
13
|
-
export type TeamServiceCreator = (db: Database) => TeamService
|
|
14
|
-
|
|
15
|
-
export class TeamService {
|
|
16
|
-
constructor(
|
|
17
|
-
public db: Database,
|
|
18
|
-
private appviewAgent: AtpAgent,
|
|
19
|
-
private appviewDid: string,
|
|
20
|
-
private createAuthHeaders: (
|
|
21
|
-
aud: string,
|
|
22
|
-
method: string,
|
|
23
|
-
) => Promise<AuthHeaders>,
|
|
24
|
-
) {}
|
|
25
|
-
|
|
26
|
-
static creator(
|
|
27
|
-
appviewAgent: AtpAgent,
|
|
28
|
-
appviewDid: string,
|
|
29
|
-
createAuthHeaders: (aud: string, method: string) => Promise<AuthHeaders>,
|
|
30
|
-
) {
|
|
31
|
-
return (db: Database) =>
|
|
32
|
-
new TeamService(db, appviewAgent, appviewDid, createAuthHeaders)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async list({
|
|
36
|
-
cursor,
|
|
37
|
-
limit = 25,
|
|
38
|
-
roles,
|
|
39
|
-
disabled,
|
|
40
|
-
q,
|
|
41
|
-
}: {
|
|
42
|
-
q?: string
|
|
43
|
-
cursor?: string
|
|
44
|
-
limit?: number
|
|
45
|
-
disabled?: boolean
|
|
46
|
-
roles?: string[]
|
|
47
|
-
}): Promise<{ members: Selectable<Member>[]; cursor?: string }> {
|
|
48
|
-
let builder = this.db.db.selectFrom('member').selectAll()
|
|
49
|
-
if (cursor) {
|
|
50
|
-
builder = builder.where('createdAt', '>', new Date(cursor))
|
|
51
|
-
}
|
|
52
|
-
if (roles !== undefined) {
|
|
53
|
-
const knownRoles = roles.filter(
|
|
54
|
-
(r) =>
|
|
55
|
-
r === 'tools.ozone.team.defs#roleAdmin' ||
|
|
56
|
-
r === 'tools.ozone.team.defs#roleModerator' ||
|
|
57
|
-
r === 'tools.ozone.team.defs#roleVerifier' ||
|
|
58
|
-
r === 'tools.ozone.team.defs#roleTriage',
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
// Optimization: no need to query to know that no values will be returned
|
|
62
|
-
if (!knownRoles.length) return { members: [] }
|
|
63
|
-
|
|
64
|
-
builder = builder.where('role', 'in', knownRoles)
|
|
65
|
-
}
|
|
66
|
-
if (disabled !== undefined) {
|
|
67
|
-
builder = builder.where('disabled', disabled ? 'is' : 'is not', true)
|
|
68
|
-
}
|
|
69
|
-
if (q) {
|
|
70
|
-
builder = builder.where((eb) =>
|
|
71
|
-
eb.or([
|
|
72
|
-
eb('handle', 'ilike', `%${q}%`),
|
|
73
|
-
eb('displayName', 'ilike', `%${q}%`),
|
|
74
|
-
]),
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const members = await builder
|
|
79
|
-
.limit(limit)
|
|
80
|
-
.orderBy('createdAt', 'asc')
|
|
81
|
-
.orderBy('handle', 'asc')
|
|
82
|
-
.execute()
|
|
83
|
-
|
|
84
|
-
return { members, cursor: members.at(-1)?.createdAt.toISOString() }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async create({
|
|
88
|
-
role,
|
|
89
|
-
did,
|
|
90
|
-
disabled,
|
|
91
|
-
updatedAt,
|
|
92
|
-
createdAt,
|
|
93
|
-
lastUpdatedBy,
|
|
94
|
-
}: Omit<Selectable<Member>, 'createdAt' | 'updatedAt'> & {
|
|
95
|
-
createdAt?: Date
|
|
96
|
-
updatedAt?: Date
|
|
97
|
-
}): Promise<Selectable<Member>> {
|
|
98
|
-
const now = new Date()
|
|
99
|
-
const newMember = await this.db.db
|
|
100
|
-
.insertInto('member')
|
|
101
|
-
.values({
|
|
102
|
-
role,
|
|
103
|
-
did,
|
|
104
|
-
disabled,
|
|
105
|
-
lastUpdatedBy,
|
|
106
|
-
updatedAt: updatedAt || now,
|
|
107
|
-
createdAt: createdAt || now,
|
|
108
|
-
})
|
|
109
|
-
.returningAll()
|
|
110
|
-
.executeTakeFirstOrThrow()
|
|
111
|
-
|
|
112
|
-
return newMember
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async upsert({
|
|
116
|
-
role,
|
|
117
|
-
did,
|
|
118
|
-
lastUpdatedBy,
|
|
119
|
-
}: Pick<
|
|
120
|
-
Selectable<Member>,
|
|
121
|
-
'role' | 'did' | 'lastUpdatedBy'
|
|
122
|
-
>): Promise<void> {
|
|
123
|
-
const now = new Date()
|
|
124
|
-
await this.db.db
|
|
125
|
-
.insertInto('member')
|
|
126
|
-
.values({
|
|
127
|
-
role,
|
|
128
|
-
did,
|
|
129
|
-
lastUpdatedBy,
|
|
130
|
-
disabled: false,
|
|
131
|
-
updatedAt: now,
|
|
132
|
-
createdAt: now,
|
|
133
|
-
})
|
|
134
|
-
.onConflict((oc) =>
|
|
135
|
-
oc.column('did').doUpdateSet({ role, updatedAt: now, lastUpdatedBy }),
|
|
136
|
-
)
|
|
137
|
-
.execute()
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async update(
|
|
141
|
-
did: string,
|
|
142
|
-
updates: Partial<
|
|
143
|
-
Pick<
|
|
144
|
-
Selectable<Member>,
|
|
145
|
-
'role' | 'disabled' | 'lastUpdatedBy' | 'updatedAt'
|
|
146
|
-
>
|
|
147
|
-
>,
|
|
148
|
-
): Promise<Selectable<Member>> {
|
|
149
|
-
const { role, disabled, lastUpdatedBy, updatedAt = new Date() } = updates
|
|
150
|
-
const updatedMember = await this.db.db
|
|
151
|
-
.updateTable('member')
|
|
152
|
-
.where('did', '=', did)
|
|
153
|
-
.set({
|
|
154
|
-
role,
|
|
155
|
-
disabled,
|
|
156
|
-
lastUpdatedBy,
|
|
157
|
-
updatedAt,
|
|
158
|
-
})
|
|
159
|
-
.returningAll()
|
|
160
|
-
.executeTakeFirstOrThrow()
|
|
161
|
-
|
|
162
|
-
return updatedMember
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async delete(did: string): Promise<void> {
|
|
166
|
-
await this.db.db.deleteFrom('member').where('did', '=', did).execute()
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async assertCanDelete(did: string): Promise<void> {
|
|
170
|
-
const memberExists = await this.doesMemberExist(did)
|
|
171
|
-
|
|
172
|
-
if (!memberExists) {
|
|
173
|
-
throw new InvalidRequestError('member not found', 'MemberNotFound')
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async doesMemberExist(did: string): Promise<boolean> {
|
|
178
|
-
const member = await this.db.db
|
|
179
|
-
.selectFrom('member')
|
|
180
|
-
.select('did')
|
|
181
|
-
.where('did', '=', did)
|
|
182
|
-
.executeTakeFirst()
|
|
183
|
-
|
|
184
|
-
return !!member
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async getMember(did: string): Promise<Selectable<Member> | undefined> {
|
|
188
|
-
const member = await this.db.db
|
|
189
|
-
.selectFrom('member')
|
|
190
|
-
.selectAll()
|
|
191
|
-
.where('did', '=', did)
|
|
192
|
-
.executeTakeFirst()
|
|
193
|
-
|
|
194
|
-
return member
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
getMemberRole(member?: Selectable<Member>) {
|
|
198
|
-
const isAdmin = member?.role === 'tools.ozone.team.defs#roleAdmin'
|
|
199
|
-
const isModerator =
|
|
200
|
-
isAdmin || member?.role === 'tools.ozone.team.defs#roleModerator'
|
|
201
|
-
const isTriage =
|
|
202
|
-
isModerator || member?.role === 'tools.ozone.team.defs#roleTriage'
|
|
203
|
-
const isVerifier =
|
|
204
|
-
isAdmin || member?.role === 'tools.ozone.team.defs#roleVerifier'
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
isModerator,
|
|
208
|
-
isAdmin,
|
|
209
|
-
isTriage,
|
|
210
|
-
isVerifier,
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// getProfiles() only allows 25 DIDs at a time so we need to query in chunks
|
|
215
|
-
async getProfiles(dids: string[]): Promise<Map<string, ProfileViewDetailed>> {
|
|
216
|
-
const profiles = new Map<string, ProfileViewDetailed>()
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const headers = await this.createAuthHeaders(
|
|
220
|
-
this.appviewDid,
|
|
221
|
-
ids.AppBskyActorGetProfiles,
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
for (const actors of chunkArray(dids, 25)) {
|
|
225
|
-
const { data } = await this.appviewAgent.getProfiles(
|
|
226
|
-
{ actors },
|
|
227
|
-
headers,
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
data.profiles.forEach((profile) => {
|
|
231
|
-
profiles.set(profile.did, profile)
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
} catch (err) {
|
|
235
|
-
httpLogger.error({ err, dids }, 'Failed to get profiles for team members')
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return profiles
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async syncMemberProfiles(): Promise<void> {
|
|
242
|
-
let lastDid = ''
|
|
243
|
-
// Max 25 profiles can be fetched at a time so let's pull 25 members at a time from the db and update their profile details
|
|
244
|
-
do {
|
|
245
|
-
const members = await this.db.db
|
|
246
|
-
.selectFrom('member')
|
|
247
|
-
.select(['did'])
|
|
248
|
-
.limit(25)
|
|
249
|
-
.$if(!!lastDid, (q) => q.where('did', '>', lastDid))
|
|
250
|
-
.orderBy('did', 'asc')
|
|
251
|
-
.execute()
|
|
252
|
-
|
|
253
|
-
const dids = members.map((member) => member.did)
|
|
254
|
-
const profiles = await this.getProfiles(dids)
|
|
255
|
-
|
|
256
|
-
for (const profile of profiles.values()) {
|
|
257
|
-
await this.db.db
|
|
258
|
-
.updateTable('member')
|
|
259
|
-
.where('did', '=', profile.did)
|
|
260
|
-
.set({
|
|
261
|
-
handle: profile.handle,
|
|
262
|
-
displayName: profile.displayName || null,
|
|
263
|
-
})
|
|
264
|
-
.execute()
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
lastDid = dids.at(-1) || ''
|
|
268
|
-
} while (lastDid)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async viewByDids(dids: string[]): Promise<Map<string, TeamMember>> {
|
|
272
|
-
if (!dids.length) return new Map()
|
|
273
|
-
const members = await this.db.db
|
|
274
|
-
.selectFrom('member')
|
|
275
|
-
.selectAll()
|
|
276
|
-
.where('did', 'in', dids)
|
|
277
|
-
.execute()
|
|
278
|
-
const memberViews = await this.view(members)
|
|
279
|
-
return new Map(memberViews.map((m) => [m.did, m]))
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async view(members: Selectable<Member>[]): Promise<TeamMember[]> {
|
|
283
|
-
const profiles = await this.getProfiles(members.map(({ did }) => did))
|
|
284
|
-
return members.map((member) => {
|
|
285
|
-
return {
|
|
286
|
-
did: member.did,
|
|
287
|
-
role: member.role,
|
|
288
|
-
disabled: member.disabled,
|
|
289
|
-
profile: profiles.get(member.did),
|
|
290
|
-
createdAt: member.createdAt.toISOString(),
|
|
291
|
-
updatedAt: member.updatedAt.toISOString(),
|
|
292
|
-
lastUpdatedBy: member.lastUpdatedBy,
|
|
293
|
-
}
|
|
294
|
-
})
|
|
295
|
-
}
|
|
296
|
-
}
|
package/src/util.ts
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { parseList } from 'structured-headers'
|
|
3
|
-
import { createRetryable } from '@atproto/common'
|
|
4
|
-
import { ResponseType, XRPCError } from '@atproto/xrpc'
|
|
5
|
-
import { Database } from './db/index.js'
|
|
6
|
-
|
|
7
|
-
export const getSigningKeyId = async (
|
|
8
|
-
db: Database,
|
|
9
|
-
signingKey: string,
|
|
10
|
-
): Promise<number> => {
|
|
11
|
-
const selectRes = await db.db
|
|
12
|
-
.selectFrom('signing_key')
|
|
13
|
-
.selectAll()
|
|
14
|
-
.where('key', '=', signingKey)
|
|
15
|
-
.executeTakeFirst()
|
|
16
|
-
if (selectRes) {
|
|
17
|
-
return selectRes.id
|
|
18
|
-
}
|
|
19
|
-
const insertRes = await db.db
|
|
20
|
-
.insertInto('signing_key')
|
|
21
|
-
.values({ key: signingKey })
|
|
22
|
-
.returningAll()
|
|
23
|
-
.executeTakeFirstOrThrow()
|
|
24
|
-
return insertRes.id
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const RETRYABLE_HTTP_STATUS_CODES = new Set([
|
|
28
|
-
408, 425, 429, 500, 502, 503, 504, 522, 524,
|
|
29
|
-
])
|
|
30
|
-
|
|
31
|
-
export const retryHttp = createRetryable((err: unknown) => {
|
|
32
|
-
if (err instanceof XRPCError) {
|
|
33
|
-
if (err.status === ResponseType.Unknown) return true
|
|
34
|
-
return RETRYABLE_HTTP_STATUS_CODES.has(err.status)
|
|
35
|
-
}
|
|
36
|
-
return false
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
export type ParsedLabelers = {
|
|
40
|
-
dids: string[]
|
|
41
|
-
redact: Set<string>
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const LABELER_HEADER_NAME = 'atproto-accept-labelers'
|
|
45
|
-
|
|
46
|
-
export const parseLabelerHeader = (
|
|
47
|
-
header: string | undefined,
|
|
48
|
-
ignoreDid?: string,
|
|
49
|
-
): ParsedLabelers | null => {
|
|
50
|
-
if (!header) return null
|
|
51
|
-
const labelerDids = new Set<string>()
|
|
52
|
-
const redactDids = new Set<string>()
|
|
53
|
-
const parsed = parseList(header)
|
|
54
|
-
for (const item of parsed) {
|
|
55
|
-
const did = item[0].toString()
|
|
56
|
-
if (!did) {
|
|
57
|
-
return null
|
|
58
|
-
}
|
|
59
|
-
if (did === ignoreDid) {
|
|
60
|
-
continue
|
|
61
|
-
}
|
|
62
|
-
labelerDids.add(did)
|
|
63
|
-
const redact = item[1].get('redact')?.valueOf()
|
|
64
|
-
if (redact === true) {
|
|
65
|
-
redactDids.add(did)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
dids: [...labelerDids],
|
|
70
|
-
redact: redactDids,
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export const defaultLabelerHeader = (dids: string[]): ParsedLabelers => {
|
|
75
|
-
return {
|
|
76
|
-
dids,
|
|
77
|
-
redact: new Set(dids),
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export const formatLabelerHeader = (parsed: ParsedLabelers): string => {
|
|
82
|
-
const parts = parsed.dids.map((did) =>
|
|
83
|
-
parsed.redact.has(did) ? `${did};redact` : did,
|
|
84
|
-
)
|
|
85
|
-
return parts.join(',')
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Utility function similar to `setInterval()`. The main difference is that the
|
|
90
|
-
* execution is controlled through a signal and that the function will wait for
|
|
91
|
-
* `interval` milliseconds *between* the end of the previous execution and the
|
|
92
|
-
* start of the next one (instead of starting the execution every `interval`
|
|
93
|
-
* milliseconds), ensuring that the function is not running concurrently.
|
|
94
|
-
*
|
|
95
|
-
* @param fn The function to execute. That function must not throw any error
|
|
96
|
-
* other than {@link signal}'s {@link AbortSignal.reason} or an {@link Error}
|
|
97
|
-
* that has the {@link signal}'s {@link AbortSignal.reason} as its
|
|
98
|
-
* {@link Error.cause}.
|
|
99
|
-
*
|
|
100
|
-
* @returns A promise that resolves when the signal is aborted, and the last
|
|
101
|
-
* execution is done.
|
|
102
|
-
*
|
|
103
|
-
* @throws {AbortSignal['reason']} if the {@link signal} is already aborted.
|
|
104
|
-
* @throws {TypeError} if {@link fn} throws an unexpected error (with the
|
|
105
|
-
* unexpected error as the {@link Error.cause}).
|
|
106
|
-
*/
|
|
107
|
-
export async function startInterval(
|
|
108
|
-
fn: (signal: AbortSignal) => void | Promise<void>,
|
|
109
|
-
interval: number,
|
|
110
|
-
signal: AbortSignal,
|
|
111
|
-
runImmediately = false,
|
|
112
|
-
) {
|
|
113
|
-
signal.throwIfAborted()
|
|
114
|
-
|
|
115
|
-
// Renaming for clarity
|
|
116
|
-
const inputSignal = signal
|
|
117
|
-
|
|
118
|
-
const intervalController = new AbortController()
|
|
119
|
-
const intervalSignal = intervalController.signal
|
|
120
|
-
|
|
121
|
-
return new Promise<void>((resolve, reject) => {
|
|
122
|
-
let timer: NodeJS.Timeout | undefined
|
|
123
|
-
|
|
124
|
-
const run = async () => {
|
|
125
|
-
// Cloning the signal for this particular run to prevent memory leaks
|
|
126
|
-
const runController = boundAbortController(intervalSignal)
|
|
127
|
-
const runSignal = runController.signal
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
await fn(runSignal)
|
|
131
|
-
} catch (err) {
|
|
132
|
-
if (err != null && isCausedBySignal(err, runSignal)) {
|
|
133
|
-
// Silently ignore the error if it is caused by the signal. At this
|
|
134
|
-
// point, the interval controller was aborted, which will cause the
|
|
135
|
-
// promise to resolve in the "finally" block bellow.
|
|
136
|
-
} else {
|
|
137
|
-
// Invalid behavior: stop the interval and reject the promise.
|
|
138
|
-
const error = new TypeError('Unexpected error', { cause: err })
|
|
139
|
-
|
|
140
|
-
// Rejecting here will make `resolve()` in the "finally" block to be a
|
|
141
|
-
// no-op. Rejecting before aborting the controller to ensure the
|
|
142
|
-
// promise does not get resolved by the `abort` event listeners.
|
|
143
|
-
reject(error)
|
|
144
|
-
|
|
145
|
-
// Using `error` as abort reason to avoid creating an AbortError.
|
|
146
|
-
intervalController.abort(error)
|
|
147
|
-
}
|
|
148
|
-
} finally {
|
|
149
|
-
// Cleanup the listeners added by `boundAbortController`
|
|
150
|
-
runController.abort()
|
|
151
|
-
|
|
152
|
-
if (intervalSignal.aborted) resolve()
|
|
153
|
-
else schedule()
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const schedule = () => {
|
|
158
|
-
assert(timer === undefined, 'unexpected state')
|
|
159
|
-
timer = setTimeout(() => {
|
|
160
|
-
timer = undefined // "running" state
|
|
161
|
-
void run()
|
|
162
|
-
}, interval)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
inputSignal.addEventListener(
|
|
166
|
-
'abort',
|
|
167
|
-
// This function will only be called if the `inputSignal` is aborted
|
|
168
|
-
// before the interval controller is aborted.
|
|
169
|
-
() => {
|
|
170
|
-
// Stop the interval, using the input signal's reason
|
|
171
|
-
intervalController.abort(inputSignal.reason)
|
|
172
|
-
|
|
173
|
-
if (timer === undefined) {
|
|
174
|
-
// `fn` is currently running; `run`'s finally block will resolve the
|
|
175
|
-
// promise.
|
|
176
|
-
} else {
|
|
177
|
-
// The execution was scheduled but not started yet. Clear the timer
|
|
178
|
-
// and resolve the promise.
|
|
179
|
-
clearTimeout(timer)
|
|
180
|
-
resolve()
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
// Remove the listener whenever the interval is aborted.
|
|
184
|
-
{ signal: intervalSignal },
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
if (runImmediately) void run()
|
|
188
|
-
else schedule()
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Determines whether the cause of an error is a signal's reason
|
|
194
|
-
*/
|
|
195
|
-
export function isCausedBySignal(err: unknown, signal: AbortSignal) {
|
|
196
|
-
if (!signal.aborted) return false
|
|
197
|
-
if (signal.reason == null) return false // Ignore nullish reasons
|
|
198
|
-
return (
|
|
199
|
-
err === signal.reason ||
|
|
200
|
-
(err instanceof Error && err.cause === signal.reason)
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Creates an AbortController that will be aborted when any of the given signals
|
|
206
|
-
* is aborted.
|
|
207
|
-
*
|
|
208
|
-
* @note Make sure to call `abortController.abort()` when you are done with
|
|
209
|
-
* the controller to avoid memory leaks.
|
|
210
|
-
*
|
|
211
|
-
* @throws if any of the input signals is already aborted.
|
|
212
|
-
*/
|
|
213
|
-
export function boundAbortController(
|
|
214
|
-
...signals: readonly (AbortSignal | undefined | null)[]
|
|
215
|
-
): AbortController {
|
|
216
|
-
for (const signal of signals) {
|
|
217
|
-
signal?.throwIfAborted()
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const abortController = new AbortController()
|
|
221
|
-
const abort = function (event: Event) {
|
|
222
|
-
abortController.abort((event.target as AbortSignal)?.reason)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
for (const signal of signals) {
|
|
226
|
-
signal?.addEventListener('abort', abort, { signal: abortController.signal })
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return abortController
|
|
230
|
-
}
|