tangled
alpha
login
or
join now
ptr.pet
/
barometer
1
fork
atom
service status on atproto
1
fork
atom
overview
issues
pulls
pipelines
feat: implement proxying state records
ptr.pet
8 months ago
982d5ceb
8974b29d
verified
This commit was signed with the committer's
known signature
.
ptr.pet
SSH Key Fingerprint:
SHA256:Abmvag+juovVufZTxyWY8KcVgrznxvBjQpJesv071Aw=
+188
-70
9 changed files
expand all
collapse all
unified
split
lib
src
index.ts
tsconfig.json
package.json
proxy
bun.lock
package.json
src
index.ts
tsconfig.json
src
index.ts
tsconfig.json
+19
-2
bun.lock
proxy/bun.lock
···
4
4
"": {
5
5
"name": "barometer",
6
6
"dependencies": {
7
7
+
"@atcute/atproto": "^3.1.1",
8
8
+
"@atcute/client": "^4.0.3",
9
9
+
"@atcute/identity": "^1.0.3",
10
10
+
"@atcute/identity-resolver": "^1.1.3",
7
11
"@atcute/lexicons": "^1.1.0",
8
8
-
"barometer-lexicon": "file:lib",
12
12
+
"@atcute/tid": "^1.0.2",
13
13
+
"barometer-lexicon": "file:../lib",
9
14
},
10
15
"devDependencies": {
11
16
"@types/bun": "latest",
···
16
21
},
17
22
},
18
23
"packages": {
24
24
+
"@atcute/atproto": ["@atcute/atproto@3.1.1", "", { "dependencies": { "@atcute/lexicons": "^1.1.0" } }, "sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg=="],
25
25
+
26
26
+
"@atcute/client": ["@atcute/client@4.0.3", "", { "dependencies": { "@atcute/identity": "^1.0.2", "@atcute/lexicons": "^1.0.3" } }, "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw=="],
27
27
+
28
28
+
"@atcute/identity": ["@atcute/identity@1.0.3", "", { "dependencies": { "@atcute/lexicons": "^1.0.4", "@badrap/valita": "^0.4.5" } }, "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw=="],
29
29
+
30
30
+
"@atcute/identity-resolver": ["@atcute/identity-resolver@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.0.4", "@atcute/util-fetch": "^1.0.1", "@badrap/valita": "^0.4.4" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA=="],
31
31
+
19
32
"@atcute/lex-cli": ["@atcute/lex-cli@2.1.1", "", { "dependencies": { "@atcute/lexicon-doc": "^1.0.2", "@badrap/valita": "^0.4.5", "@externdefs/collider": "^0.3.0", "picocolors": "^1.1.1", "prettier": "^3.5.3" }, "bin": { "lex-cli": "cli.mjs" } }, "sha512-QaR0sOP8Z24opGHKsSfleDbP/ahUb6HECkVaOqSwG7ORZzbLK1w0265o1BRjCVr2dT6FxlsMUa2Ge85JMA9bxg=="],
20
33
21
34
"@atcute/lexicon-doc": ["@atcute/lexicon-doc@1.0.3", "", { "dependencies": { "@badrap/valita": "^0.4.5" } }, "sha512-U7rinsTOwXGGcrF6/s7GzTXargcQpDr4BTrj5ci/XTK+POEK5jpcI+Ag1fF932pBX3k97em6y4TWwTSO8M/McQ=="],
22
35
23
36
"@atcute/lexicons": ["@atcute/lexicons@1.1.0", "", { "dependencies": { "esm-env": "^1.2.2" } }, "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q=="],
24
37
38
38
+
"@atcute/tid": ["@atcute/tid@1.0.2", "", {}, "sha512-ahmjroNyeDPJhtuf3+HTJropaH04HmJ8fhntDu73Gpz/RkAF7+nkz6kcP2QTgfvMCgMPAJUdskAAP82GPDTY9w=="],
39
39
+
40
40
+
"@atcute/util-fetch": ["@atcute/util-fetch@1.0.1", "", { "dependencies": { "@badrap/valita": "^0.4.2" } }, "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow=="],
41
41
+
25
42
"@badrap/valita": ["@badrap/valita@0.4.5", "", {}, "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ=="],
26
43
27
44
"@externdefs/collider": ["@externdefs/collider@0.3.0", "", { "peerDependencies": { "@badrap/valita": "^0.4.4" } }, "sha512-x5CpeZ4c8n+1wMFthUMWSQKqCGcQo52/Qbda5ES+JFRRg/D8Ep6/JOvUUq5HExFuv/wW+6UYG2U/mXzw0IAd8Q=="],
···
32
49
33
50
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
34
51
35
35
-
"barometer-lexicon": ["barometer-lexicon@file:lib", { "dependencies": { "@atcute/lexicons": "^1.1.0" }, "devDependencies": { "@atcute/lex-cli": "^2.1.1", "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }],
52
52
+
"barometer-lexicon": ["barometer-lexicon@file:../lib", { "dependencies": { "@atcute/lexicons": "^1.1.0" }, "devDependencies": { "@atcute/lex-cli": "^2.1.1", "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }],
36
53
37
54
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
38
55
+1
-1
lib/src/index.ts
···
1
1
-
export * from "./lexicons/index.ts";
1
1
+
export * from "./lexicons/index.js";
+12
-20
lib/tsconfig.json
···
1
1
{
2
2
"compilerOptions": {
3
3
-
// Environment setup & latest features
4
4
-
"lib": ["ESNext"],
3
3
+
"outDir": "dist/",
4
4
+
"esModuleInterop": true,
5
5
+
"skipLibCheck": true,
5
6
"target": "ESNext",
6
6
-
"module": "Preserve",
7
7
-
"moduleDetection": "force",
8
8
-
"jsx": "react-jsx",
9
7
"allowJs": true,
10
10
-
11
11
-
// Bundler mode
12
12
-
"moduleResolution": "bundler",
13
13
-
"allowImportingTsExtensions": true,
8
8
+
"resolveJsonModule": true,
9
9
+
"moduleDetection": "force",
10
10
+
"isolatedModules": true,
14
11
"verbatimModuleSyntax": true,
15
15
-
"noEmit": true,
16
16
-
17
17
-
// Best practices
18
12
"strict": true,
19
19
-
"skipLibCheck": true,
20
20
-
"noFallthroughCasesInSwitch": true,
21
21
-
"noUncheckedIndexedAccess": true,
22
13
"noImplicitOverride": true,
23
23
-
24
24
-
// Some stricter flags (disabled by default)
25
25
-
"noUnusedLocals": false,
26
26
-
"noUnusedParameters": false,
27
27
-
"noPropertyAccessFromIndexSignature": false
14
14
+
"noUnusedLocals": true,
15
15
+
"noUnusedParameters": true,
16
16
+
"noFallthroughCasesInSwitch": true,
17
17
+
"module": "NodeNext",
18
18
+
"sourceMap": true,
19
19
+
"declaration": true
28
20
},
29
21
"include": ["src"]
30
22
}
-16
package.json
···
1
1
-
{
2
2
-
"name": "barometer",
3
3
-
"module": "index.ts",
4
4
-
"type": "module",
5
5
-
"private": true,
6
6
-
"devDependencies": {
7
7
-
"@types/bun": "latest"
8
8
-
},
9
9
-
"peerDependencies": {
10
10
-
"typescript": "^5"
11
11
-
},
12
12
-
"dependencies": {
13
13
-
"@atcute/lexicons": "^1.1.0",
14
14
-
"barometer-lexicon": "file:lib"
15
15
-
}
16
16
-
}
+24
proxy/package.json
···
1
1
+
{
2
2
+
"name": "barometer",
3
3
+
"module": "index.ts",
4
4
+
"type": "module",
5
5
+
"private": true,
6
6
+
"scripts": {
7
7
+
"dev": "bun --watch src/index.ts"
8
8
+
},
9
9
+
"devDependencies": {
10
10
+
"@types/bun": "latest"
11
11
+
},
12
12
+
"peerDependencies": {
13
13
+
"typescript": "^5"
14
14
+
},
15
15
+
"dependencies": {
16
16
+
"@atcute/atproto": "^3.1.1",
17
17
+
"@atcute/client": "^4.0.3",
18
18
+
"@atcute/identity": "^1.0.3",
19
19
+
"@atcute/identity-resolver": "^1.1.3",
20
20
+
"@atcute/lexicons": "^1.1.0",
21
21
+
"@atcute/tid": "^1.0.2",
22
22
+
"barometer-lexicon": "file:../lib"
23
23
+
}
24
24
+
}
+102
proxy/src/index.ts
···
1
1
+
import {
2
2
+
Client,
3
3
+
CredentialManager,
4
4
+
ok,
5
5
+
simpleFetchHandler,
6
6
+
} from "@atcute/client";
7
7
+
import { getPdsEndpoint } from "@atcute/identity";
8
8
+
import {
9
9
+
CompositeDidDocumentResolver,
10
10
+
PlcDidDocumentResolver,
11
11
+
WebDidDocumentResolver,
12
12
+
} from "@atcute/identity-resolver";
13
13
+
import { isDid, type AtprotoDid } from "@atcute/lexicons/syntax";
14
14
+
import { env } from "process";
15
15
+
import type {} from "@atcute/atproto";
16
16
+
import {} from "barometer-lexicon";
17
17
+
import { SystemsGazeBarometerState } from "barometer-lexicon";
18
18
+
import { now as generateTid } from "@atcute/tid";
19
19
+
import { is, safeParse } from "@atcute/lexicons";
20
20
+
21
21
+
interface Config {
22
22
+
repoDid: AtprotoDid;
23
23
+
appPass: string;
24
24
+
}
25
25
+
26
26
+
const getConfig = (prefix: string): Config => {
27
27
+
const get = <Value>(
28
28
+
name: string,
29
29
+
check: (value: unknown) => boolean = (value) =>
30
30
+
typeof value !== "undefined",
31
31
+
): Value => {
32
32
+
const value = env[`${prefix}${name}`];
33
33
+
if (check(value)) {
34
34
+
return value as Value;
35
35
+
}
36
36
+
throw `config key ${name} is invalid`;
37
37
+
};
38
38
+
return {
39
39
+
repoDid: get("REPO_DID", isDid),
40
40
+
appPass: get("APP_PASSWORD"),
41
41
+
};
42
42
+
};
43
43
+
44
44
+
const config = getConfig("BAROMETER_");
45
45
+
46
46
+
const docResolver = new CompositeDidDocumentResolver({
47
47
+
methods: {
48
48
+
plc: new PlcDidDocumentResolver(),
49
49
+
web: new WebDidDocumentResolver(),
50
50
+
},
51
51
+
});
52
52
+
53
53
+
const pdsUrl = getPdsEndpoint(await docResolver.resolve(config.repoDid));
54
54
+
if (pdsUrl === undefined) {
55
55
+
throw `no pds found`;
56
56
+
}
57
57
+
console.info(`pds is ${pdsUrl}`);
58
58
+
59
59
+
const creds = new CredentialManager({ service: pdsUrl });
60
60
+
const session = await creds.login({
61
61
+
identifier: config.repoDid,
62
62
+
password: config.appPass,
63
63
+
});
64
64
+
const atpClient = new Client({ handler: creds });
65
65
+
66
66
+
const server = Bun.serve({
67
67
+
routes: {
68
68
+
"/push": {
69
69
+
POST: async (req) => {
70
70
+
const maybeState = safeParse(
71
71
+
SystemsGazeBarometerState.mainSchema,
72
72
+
await req.json(),
73
73
+
);
74
74
+
if (!maybeState.ok) {
75
75
+
return new Response(
76
76
+
JSON.stringify({
77
77
+
msg: `invalid state: ${maybeState.message}`,
78
78
+
issues: maybeState.issues,
79
79
+
}),
80
80
+
{ status: 400 },
81
81
+
);
82
82
+
}
83
83
+
const state = maybeState.value;
84
84
+
const result = await ok(
85
85
+
atpClient.post("com.atproto.repo.putRecord", {
86
86
+
input: {
87
87
+
collection: state.$type,
88
88
+
record: state,
89
89
+
repo: config.repoDid,
90
90
+
rkey: generateTid(),
91
91
+
},
92
92
+
}),
93
93
+
);
94
94
+
return new Response(
95
95
+
JSON.stringify({ cid: result.cid, uri: result.uri }),
96
96
+
);
97
97
+
},
98
98
+
},
99
99
+
},
100
100
+
});
101
101
+
102
102
+
console.log(`server running on http://localhost:${server.port}`);
+30
proxy/tsconfig.json
···
1
1
+
{
2
2
+
"compilerOptions": {
3
3
+
// Environment setup & latest features
4
4
+
"lib": ["ESNext"],
5
5
+
"target": "ESNext",
6
6
+
"module": "Preserve",
7
7
+
"moduleDetection": "force",
8
8
+
"jsx": "react-jsx",
9
9
+
"allowJs": true,
10
10
+
11
11
+
// Bundler mode
12
12
+
"moduleResolution": "bundler",
13
13
+
"allowImportingTsExtensions": true,
14
14
+
"verbatimModuleSyntax": true,
15
15
+
"noEmit": true,
16
16
+
17
17
+
// Best practices
18
18
+
"strict": true,
19
19
+
"skipLibCheck": true,
20
20
+
"noFallthroughCasesInSwitch": true,
21
21
+
"noUncheckedIndexedAccess": true,
22
22
+
"noImplicitOverride": true,
23
23
+
24
24
+
// Some stricter flags (disabled by default)
25
25
+
"noUnusedLocals": false,
26
26
+
"noUnusedParameters": false,
27
27
+
"noPropertyAccessFromIndexSignature": false
28
28
+
},
29
29
+
"include": ["src/**/*.ts"]
30
30
+
}
-1
src/index.ts
···
1
1
-
console.log("Hello via Bun!");
-30
tsconfig.json
···
1
1
-
{
2
2
-
"compilerOptions": {
3
3
-
// Environment setup & latest features
4
4
-
"lib": ["ESNext"],
5
5
-
"target": "ESNext",
6
6
-
"module": "Preserve",
7
7
-
"moduleDetection": "force",
8
8
-
"jsx": "react-jsx",
9
9
-
"allowJs": true,
10
10
-
11
11
-
// Bundler mode
12
12
-
"moduleResolution": "bundler",
13
13
-
"allowImportingTsExtensions": true,
14
14
-
"verbatimModuleSyntax": true,
15
15
-
"noEmit": true,
16
16
-
17
17
-
// Best practices
18
18
-
"strict": true,
19
19
-
"skipLibCheck": true,
20
20
-
"noFallthroughCasesInSwitch": true,
21
21
-
"noUncheckedIndexedAccess": true,
22
22
-
"noImplicitOverride": true,
23
23
-
24
24
-
// Some stricter flags (disabled by default)
25
25
-
"noUnusedLocals": false,
26
26
-
"noUnusedParameters": false,
27
27
-
"noPropertyAccessFromIndexSignature": false
28
28
-
},
29
29
-
"include": ["src/**/*.ts"]
30
30
-
}