a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(identity): stop normalizing service array

Mary 199ecf0f e8708527

+180 -120
+5
.changeset/cruel-rockets-yell.md
··· 1 + --- 2 + '@atcute/identity': patch 3 + --- 4 + 5 + stop normalizing service array
+89 -99
packages/identity/identity/lib/typedefs.test.ts
··· 29 29 ], 30 30 }); 31 31 32 - expect(doc).toMatchInlineSnapshot(` 33 - { 34 - "@context": [ 35 - "https://www.w3.org/ns/did/v1", 36 - "https://w3id.org/security/multikey/v1", 37 - "https://w3id.org/security/suites/secp256k1-2019/v1", 38 - ], 39 - "alsoKnownAs": [ 40 - "at://mary.my.id", 41 - ], 42 - "id": "did:plc:ia76kvnndjutgedggx2ibrem", 43 - "service": [ 44 - { 45 - "id": "did:plc:ia76kvnndjutgedggx2ibrem#atproto_pds", 46 - "serviceEndpoint": "https://porcini.us-east.host.bsky.network", 47 - "type": "AtprotoPersonalDataServer", 48 - }, 49 - ], 50 - "verificationMethod": [ 51 - { 52 - "controller": "did:plc:ia76kvnndjutgedggx2ibrem", 53 - "id": "did:plc:ia76kvnndjutgedggx2ibrem#atproto", 54 - "publicKeyMultibase": "zQ3shuqiNQXNGKBBbNvPhcaZy8DjP3BF3yhmSeAjFXQjgPJrG", 55 - "type": "Multikey", 56 - }, 57 - ], 58 - } 59 - `); 32 + expect(doc).toEqual({ 33 + '@context': [ 34 + 'https://www.w3.org/ns/did/v1', 35 + 'https://w3id.org/security/multikey/v1', 36 + 'https://w3id.org/security/suites/secp256k1-2019/v1', 37 + ], 38 + alsoKnownAs: ['at://mary.my.id'], 39 + id: 'did:plc:ia76kvnndjutgedggx2ibrem', 40 + service: [ 41 + { 42 + id: '#atproto_pds', 43 + serviceEndpoint: 'https://porcini.us-east.host.bsky.network', 44 + type: 'AtprotoPersonalDataServer', 45 + }, 46 + ], 47 + verificationMethod: [ 48 + { 49 + controller: 'did:plc:ia76kvnndjutgedggx2ibrem', 50 + id: 'did:plc:ia76kvnndjutgedggx2ibrem#atproto', 51 + publicKeyMultibase: 'zQ3shuqiNQXNGKBBbNvPhcaZy8DjP3BF3yhmSeAjFXQjgPJrG', 52 + type: 'Multikey', 53 + }, 54 + ], 55 + }); 60 56 }); 61 57 62 58 it('parses a did:plc document containing a labeler', () => { ··· 96 92 ], 97 93 }); 98 94 99 - expect(doc).toMatchInlineSnapshot(` 100 - { 101 - "@context": [ 102 - "https://www.w3.org/ns/did/v1", 103 - "https://w3id.org/security/multikey/v1", 104 - "https://w3id.org/security/suites/secp256k1-2019/v1", 105 - ], 106 - "alsoKnownAs": [ 107 - "at://pronouns.diy", 108 - ], 109 - "id": "did:plc:wkoofae5uytcm7bjncmev6n6", 110 - "service": [ 111 - { 112 - "id": "did:plc:wkoofae5uytcm7bjncmev6n6#atproto_pds", 113 - "serviceEndpoint": "https://pds.bsky.mom", 114 - "type": "AtprotoPersonalDataServer", 115 - }, 116 - { 117 - "id": "did:plc:wkoofae5uytcm7bjncmev6n6#atproto_labeler", 118 - "serviceEndpoint": "https://api.pronouns.diy", 119 - "type": "AtprotoLabeler", 120 - }, 121 - ], 122 - "verificationMethod": [ 123 - { 124 - "controller": "did:plc:wkoofae5uytcm7bjncmev6n6", 125 - "id": "did:plc:wkoofae5uytcm7bjncmev6n6#atproto", 126 - "publicKeyMultibase": "zQ3sho8kubdqeS5wbxPDpNBBqg2tvJTKF1jovJKzQzhu4S8fH", 127 - "type": "Multikey", 128 - }, 129 - { 130 - "controller": "did:plc:wkoofae5uytcm7bjncmev6n6", 131 - "id": "did:plc:wkoofae5uytcm7bjncmev6n6#atproto_label", 132 - "publicKeyMultibase": "zQ3shQo2ZK9ZwNRxkEM1sSkpJKfx1NN6WWcvtMTDyJeCwPB7o", 133 - "type": "Multikey", 134 - }, 135 - ], 136 - } 137 - `); 95 + expect(doc).toEqual({ 96 + '@context': [ 97 + 'https://www.w3.org/ns/did/v1', 98 + 'https://w3id.org/security/multikey/v1', 99 + 'https://w3id.org/security/suites/secp256k1-2019/v1', 100 + ], 101 + alsoKnownAs: ['at://pronouns.diy'], 102 + id: 'did:plc:wkoofae5uytcm7bjncmev6n6', 103 + service: [ 104 + { 105 + id: '#atproto_pds', 106 + serviceEndpoint: 'https://pds.bsky.mom', 107 + type: 'AtprotoPersonalDataServer', 108 + }, 109 + { 110 + id: '#atproto_labeler', 111 + serviceEndpoint: 'https://api.pronouns.diy', 112 + type: 'AtprotoLabeler', 113 + }, 114 + ], 115 + verificationMethod: [ 116 + { 117 + controller: 'did:plc:wkoofae5uytcm7bjncmev6n6', 118 + id: 'did:plc:wkoofae5uytcm7bjncmev6n6#atproto', 119 + publicKeyMultibase: 'zQ3sho8kubdqeS5wbxPDpNBBqg2tvJTKF1jovJKzQzhu4S8fH', 120 + type: 'Multikey', 121 + }, 122 + { 123 + controller: 'did:plc:wkoofae5uytcm7bjncmev6n6', 124 + id: 'did:plc:wkoofae5uytcm7bjncmev6n6#atproto_label', 125 + publicKeyMultibase: 'zQ3shQo2ZK9ZwNRxkEM1sSkpJKfx1NN6WWcvtMTDyJeCwPB7o', 126 + type: 'Multikey', 127 + }, 128 + ], 129 + }); 138 130 }); 139 131 140 132 it('parses a did:web document', () => { ··· 169 161 ], 170 162 }); 171 163 172 - expect(doc).toMatchInlineSnapshot(` 173 - { 174 - "@context": [ 175 - "https://www.w3.org/ns/did/v1", 176 - "https://w3id.org/security/multikey/v1", 177 - "https://w3id.org/security/suites/secp256k1-2019/v1", 178 - ], 179 - "alsoKnownAs": [ 180 - "at://didd.uk", 181 - "did:plc:kv7sv4lynbv5s6gdhn5r5vcw", 182 - "web+ap://bsky.brid.gy/@ducky.ws", 183 - "web+ap://fedia.social/@theducky", 184 - "https://t.me/theducky", 185 - ], 186 - "id": "did:web:didd.uk", 187 - "service": [ 188 - { 189 - "id": "did:web:didd.uk#atproto_pds", 190 - "serviceEndpoint": "https://zio.blue", 191 - "type": "AtprotoPersonalDataServer", 192 - }, 193 - ], 194 - "verificationMethod": [ 195 - { 196 - "controller": "did:web:didd.uk", 197 - "id": "did:web:didd.uk#atproto", 198 - "publicKeyMultibase": "zQ3shYRepkfnXhDjKBmvBVNtu2tswxPjjTDgKWTUcuFdt7xtH", 199 - "type": "Multikey", 200 - }, 201 - ], 202 - } 203 - `); 164 + expect(doc).toEqual({ 165 + '@context': [ 166 + 'https://www.w3.org/ns/did/v1', 167 + 'https://w3id.org/security/multikey/v1', 168 + 'https://w3id.org/security/suites/secp256k1-2019/v1', 169 + ], 170 + alsoKnownAs: [ 171 + 'at://didd.uk', 172 + 'did:plc:kv7sv4lynbv5s6gdhn5r5vcw', 173 + 'web+ap://bsky.brid.gy/@ducky.ws', 174 + 'web+ap://fedia.social/@theducky', 175 + 'https://t.me/theducky', 176 + ], 177 + id: 'did:web:didd.uk', 178 + service: [ 179 + { 180 + id: '#atproto_pds', 181 + serviceEndpoint: 'https://zio.blue', 182 + type: 'AtprotoPersonalDataServer', 183 + }, 184 + ], 185 + verificationMethod: [ 186 + { 187 + controller: 'did:web:didd.uk', 188 + id: 'did:web:didd.uk#atproto', 189 + publicKeyMultibase: 'zQ3shYRepkfnXhDjKBmvBVNtu2tswxPjjTDgKWTUcuFdt7xtH', 190 + type: 'Multikey', 191 + }, 192 + ], 193 + }); 204 194 }); 205 195 });
+11 -14
packages/identity/identity/lib/typedefs.ts
··· 114 114 .chain((input) => { 115 115 const { id: did, service: services } = input; 116 116 117 - let newServices: t.Service[] | undefined; 117 + if (services?.length) { 118 + const len = services.length; 119 + const identifiers = new Array(len); 118 120 119 - if (services) { 120 - for (let i = 0, len = services.length; i < len; i++) { 121 + for (let i = 0; i < len; i++) { 121 122 const service = services[i]; 122 123 123 124 let id = service.id; 124 125 if (id[0] === '#') { 125 126 id = did + id; 127 + } 126 128 127 - if (newServices !== undefined) { 128 - newServices[i] = { ...service, id }; 129 - } else { 130 - newServices = services.with(i, { ...service, id }); 131 - } 132 - } 129 + identifiers[i] = id; 130 + } 131 + 132 + for (let i = 0; i < len; i++) { 133 + const id = identifiers[i]; 133 134 134 135 for (let j = 0; j < i; j++) { 135 - if (id === (newServices ?? services)[j].id) { 136 + if (id === identifiers[j]) { 136 137 return v.err({ 137 138 message: `duplicate "${id}" service`, 138 139 path: ['service', i, 'id'], ··· 140 141 } 141 142 } 142 143 } 143 - } 144 - 145 - if (newServices !== undefined) { 146 - return v.ok({ ...input, service: newServices }); 147 144 } 148 145 149 146 return v.ok(input);
+71
packages/identity/identity/lib/utils.test.ts
··· 1 + import { describe, it, expect } from 'bun:test'; 2 + 3 + import { didDocument } from './typedefs.js'; 4 + import { 5 + getAtprotoLabelerVerificationMaterial, 6 + getAtprotoVerificationMaterial, 7 + getLabelerEndpoint, 8 + getPdsEndpoint, 9 + } from './utils.js'; 10 + 11 + const PRONOUNS_LABELER_DID_DOC = didDocument.parse({ 12 + '@context': [ 13 + 'https://www.w3.org/ns/did/v1', 14 + 'https://w3id.org/security/multikey/v1', 15 + 'https://w3id.org/security/suites/secp256k1-2019/v1', 16 + ], 17 + id: 'did:plc:wkoofae5uytcm7bjncmev6n6', 18 + alsoKnownAs: ['at://pronouns.diy'], 19 + verificationMethod: [ 20 + { 21 + id: 'did:plc:wkoofae5uytcm7bjncmev6n6#atproto', 22 + type: 'Multikey', 23 + controller: 'did:plc:wkoofae5uytcm7bjncmev6n6', 24 + publicKeyMultibase: 'zQ3sho8kubdqeS5wbxPDpNBBqg2tvJTKF1jovJKzQzhu4S8fH', 25 + }, 26 + { 27 + id: 'did:plc:wkoofae5uytcm7bjncmev6n6#atproto_label', 28 + type: 'Multikey', 29 + controller: 'did:plc:wkoofae5uytcm7bjncmev6n6', 30 + publicKeyMultibase: 'zQ3shQo2ZK9ZwNRxkEM1sSkpJKfx1NN6WWcvtMTDyJeCwPB7o', 31 + }, 32 + ], 33 + service: [ 34 + { 35 + id: '#atproto_pds', 36 + type: 'AtprotoPersonalDataServer', 37 + serviceEndpoint: 'https://pds.bsky.mom', 38 + }, 39 + { 40 + id: '#atproto_labeler', 41 + type: 'AtprotoLabeler', 42 + serviceEndpoint: 'https://api.pronouns.diy', 43 + }, 44 + ], 45 + }); 46 + 47 + describe('getAtprotoServiceEndpoint', () => { 48 + it('grabs a PDS', () => { 49 + expect(getPdsEndpoint(PRONOUNS_LABELER_DID_DOC)).toBe('https://pds.bsky.mom'); 50 + }); 51 + 52 + it('grabs a labeler', () => { 53 + expect(getLabelerEndpoint(PRONOUNS_LABELER_DID_DOC)).toBe('https://api.pronouns.diy'); 54 + }); 55 + }); 56 + 57 + describe('getVerificationMaterial', () => { 58 + it('grabs PDS signing keys', () => { 59 + expect(getAtprotoVerificationMaterial(PRONOUNS_LABELER_DID_DOC)).toEqual({ 60 + type: 'Multikey', 61 + publicKeyMultibase: 'zQ3sho8kubdqeS5wbxPDpNBBqg2tvJTKF1jovJKzQzhu4S8fH', 62 + }); 63 + }); 64 + 65 + it('grabs labeler signing keys', () => { 66 + expect(getAtprotoLabelerVerificationMaterial(PRONOUNS_LABELER_DID_DOC)).toEqual({ 67 + type: 'Multikey', 68 + publicKeyMultibase: 'zQ3shQo2ZK9ZwNRxkEM1sSkpJKfx1NN6WWcvtMTDyJeCwPB7o', 69 + }); 70 + }); 71 + });
+4 -7
packages/identity/identity/lib/utils.ts
··· 71 71 return; 72 72 } 73 73 74 - const expectedId = doc.id + predicate.id; 75 - const expectedType = predicate.type; 76 - 77 74 for (let idx = 0, len = services.length; idx < len; idx++) { 78 75 const { id, type, serviceEndpoint } = services[idx]; 79 76 80 - if (id !== expectedId) { 77 + if (id !== predicate.id && id !== doc.id + predicate.id) { 81 78 continue; 82 79 } 83 80 84 - if (expectedType !== undefined) { 81 + if (predicate.type !== undefined) { 85 82 if (Array.isArray(type)) { 86 - if (!type.includes(expectedType)) { 83 + if (!type.includes(predicate.type)) { 87 84 continue; 88 85 } 89 86 } else { 90 - if (type !== expectedType) { 87 + if (type !== predicate.type) { 91 88 continue; 92 89 } 93 90 }