@kodelyth/tlon 2026.5.42 → 2026.6.2
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/klaw.plugin.json +203 -3
- package/package.json +19 -6
- package/api.ts +0 -16
- package/channel-plugin-api.ts +0 -1
- package/doctor-contract-api.ts +0 -1
- package/index.ts +0 -16
- package/runtime-api.ts +0 -17
- package/setup-api.ts +0 -2
- package/setup-entry.ts +0 -9
- package/src/account-fields.ts +0 -31
- package/src/channel.message-adapter.test.ts +0 -145
- package/src/channel.runtime.ts +0 -259
- package/src/channel.ts +0 -192
- package/src/config-schema.ts +0 -54
- package/src/core.test.ts +0 -298
- package/src/doctor-contract.ts +0 -9
- package/src/doctor.test.ts +0 -46
- package/src/doctor.ts +0 -10
- package/src/logger-runtime.ts +0 -1
- package/src/monitor/approval-runtime.ts +0 -363
- package/src/monitor/approval.test.ts +0 -33
- package/src/monitor/approval.ts +0 -283
- package/src/monitor/authorization.ts +0 -30
- package/src/monitor/cites.ts +0 -54
- package/src/monitor/discovery.ts +0 -68
- package/src/monitor/history.ts +0 -226
- package/src/monitor/index.ts +0 -1523
- package/src/monitor/media.test.ts +0 -80
- package/src/monitor/media.ts +0 -156
- package/src/monitor/processed-messages.test.ts +0 -58
- package/src/monitor/processed-messages.ts +0 -89
- package/src/monitor/settings-helpers.test.ts +0 -113
- package/src/monitor/settings-helpers.ts +0 -158
- package/src/monitor/utils.ts +0 -402
- package/src/runtime.ts +0 -9
- package/src/security.test.ts +0 -658
- package/src/session-route.ts +0 -40
- package/src/settings.ts +0 -391
- package/src/setup-core.ts +0 -231
- package/src/setup-surface.ts +0 -99
- package/src/targets.ts +0 -102
- package/src/tlon-api.test.ts +0 -572
- package/src/tlon-api.ts +0 -389
- package/src/types.ts +0 -160
- package/src/urbit/auth.ssrf.test.ts +0 -45
- package/src/urbit/auth.ts +0 -48
- package/src/urbit/base-url.test.ts +0 -48
- package/src/urbit/base-url.ts +0 -61
- package/src/urbit/channel-ops.test.ts +0 -36
- package/src/urbit/channel-ops.ts +0 -149
- package/src/urbit/context.ts +0 -50
- package/src/urbit/errors.ts +0 -51
- package/src/urbit/fetch.ts +0 -38
- package/src/urbit/foreigns.ts +0 -49
- package/src/urbit/send.test.ts +0 -83
- package/src/urbit/send.ts +0 -228
- package/src/urbit/sse-client.test.ts +0 -234
- package/src/urbit/sse-client.ts +0 -492
- package/src/urbit/story.ts +0 -332
- package/src/urbit/upload.test.ts +0 -155
- package/src/urbit/upload.ts +0 -60
- package/test-api.ts +0 -1
- package/tsconfig.json +0 -16
package/src/urbit/base-url.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { isBlockedHostnameOrIp } from "klaw/plugin-sdk/ssrf-runtime";
|
|
2
|
-
|
|
3
|
-
type UrbitBaseUrlValidation =
|
|
4
|
-
| { ok: true; baseUrl: string; hostname: string }
|
|
5
|
-
| { ok: false; error: string };
|
|
6
|
-
|
|
7
|
-
function hasScheme(value: string): boolean {
|
|
8
|
-
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function normalizeUrbitHostname(hostname: string | undefined): string {
|
|
12
|
-
return (hostname ?? "").trim().toLowerCase().replace(/\.$/, "");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function validateUrbitBaseUrl(raw: string): UrbitBaseUrlValidation {
|
|
16
|
-
const trimmed = raw.trim();
|
|
17
|
-
if (!trimmed) {
|
|
18
|
-
return { ok: false, error: "Required" };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const candidate = hasScheme(trimmed) ? trimmed : `https://${trimmed}`;
|
|
22
|
-
|
|
23
|
-
let parsed: URL;
|
|
24
|
-
try {
|
|
25
|
-
parsed = new URL(candidate);
|
|
26
|
-
} catch {
|
|
27
|
-
return { ok: false, error: "Invalid URL" };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
31
|
-
return { ok: false, error: "URL must use http:// or https://" };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (parsed.username || parsed.password) {
|
|
35
|
-
return { ok: false, error: "URL must not include credentials" };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const hostname = normalizeUrbitHostname(parsed.hostname);
|
|
39
|
-
if (!hostname) {
|
|
40
|
-
return { ok: false, error: "Invalid hostname" };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Normalize to origin so callers can't smuggle paths/query fragments into the base URL,
|
|
44
|
-
// and strip a trailing dot from the hostname (DNS root label).
|
|
45
|
-
const isIpv6 = hostname.includes(":");
|
|
46
|
-
const host = parsed.port
|
|
47
|
-
? `${isIpv6 ? `[${hostname}]` : hostname}:${parsed.port}`
|
|
48
|
-
: isIpv6
|
|
49
|
-
? `[${hostname}]`
|
|
50
|
-
: hostname;
|
|
51
|
-
|
|
52
|
-
return { ok: true, baseUrl: `${parsed.protocol}//${host}`, hostname };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function isBlockedUrbitHostname(hostname: string): boolean {
|
|
56
|
-
const normalized = normalizeUrbitHostname(hostname);
|
|
57
|
-
if (!normalized) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
return isBlockedHostnameOrIp(normalized);
|
|
61
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { scryUrbitPath } from "./channel-ops.js";
|
|
3
|
-
import { urbitFetch } from "./fetch.js";
|
|
4
|
-
|
|
5
|
-
vi.mock("./fetch.js", () => ({
|
|
6
|
-
urbitFetch: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
describe("Urbit channel operations", () => {
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
vi.mocked(urbitFetch).mockReset();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("wraps malformed scry response JSON", async () => {
|
|
15
|
-
const release = vi.fn().mockResolvedValue(undefined);
|
|
16
|
-
vi.mocked(urbitFetch).mockResolvedValue({
|
|
17
|
-
response: new Response("{not json", {
|
|
18
|
-
status: 200,
|
|
19
|
-
headers: { "content-type": "application/json" },
|
|
20
|
-
}),
|
|
21
|
-
finalUrl: "https://example.com/~/scry/chat/inbox.json",
|
|
22
|
-
release,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
await expect(
|
|
26
|
-
scryUrbitPath(
|
|
27
|
-
{
|
|
28
|
-
baseUrl: "https://example.com",
|
|
29
|
-
cookie: "urbauth-~zod=123",
|
|
30
|
-
},
|
|
31
|
-
{ path: "/chat/inbox.json", auditContext: "test" },
|
|
32
|
-
),
|
|
33
|
-
).rejects.toThrow("Urbit scry response was malformed JSON for path /chat/inbox.json");
|
|
34
|
-
expect(release).toHaveBeenCalledTimes(1);
|
|
35
|
-
});
|
|
36
|
-
});
|
package/src/urbit/channel-ops.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import type { LookupFn, SsrFPolicy } from "klaw/plugin-sdk/ssrf-runtime";
|
|
2
|
-
import { UrbitHttpError } from "./errors.js";
|
|
3
|
-
import { urbitFetch } from "./fetch.js";
|
|
4
|
-
|
|
5
|
-
type UrbitChannelDeps = {
|
|
6
|
-
baseUrl: string;
|
|
7
|
-
cookie: string;
|
|
8
|
-
ship: string;
|
|
9
|
-
channelId: string;
|
|
10
|
-
ssrfPolicy?: SsrFPolicy;
|
|
11
|
-
lookupFn?: LookupFn;
|
|
12
|
-
fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
async function putUrbitChannel(
|
|
16
|
-
deps: UrbitChannelDeps,
|
|
17
|
-
params: { body: unknown; auditContext: string },
|
|
18
|
-
) {
|
|
19
|
-
return await urbitFetch({
|
|
20
|
-
baseUrl: deps.baseUrl,
|
|
21
|
-
path: `/~/channel/${deps.channelId}`,
|
|
22
|
-
init: {
|
|
23
|
-
method: "PUT",
|
|
24
|
-
headers: {
|
|
25
|
-
"Content-Type": "application/json",
|
|
26
|
-
Cookie: deps.cookie,
|
|
27
|
-
},
|
|
28
|
-
body: JSON.stringify(params.body),
|
|
29
|
-
},
|
|
30
|
-
ssrfPolicy: deps.ssrfPolicy,
|
|
31
|
-
lookupFn: deps.lookupFn,
|
|
32
|
-
fetchImpl: deps.fetchImpl,
|
|
33
|
-
timeoutMs: 30_000,
|
|
34
|
-
auditContext: params.auditContext,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function pokeUrbitChannel(
|
|
39
|
-
deps: UrbitChannelDeps,
|
|
40
|
-
params: { app: string; mark: string; json: unknown; auditContext: string },
|
|
41
|
-
): Promise<number> {
|
|
42
|
-
const pokeId = Date.now();
|
|
43
|
-
const pokeData = {
|
|
44
|
-
id: pokeId,
|
|
45
|
-
action: "poke",
|
|
46
|
-
ship: deps.ship,
|
|
47
|
-
app: params.app,
|
|
48
|
-
mark: params.mark,
|
|
49
|
-
json: params.json,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const { response, release } = await putUrbitChannel(deps, {
|
|
53
|
-
body: [pokeData],
|
|
54
|
-
auditContext: params.auditContext,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
if (!response.ok && response.status !== 204) {
|
|
59
|
-
const errorText = await response.text().catch(() => "");
|
|
60
|
-
throw new Error(`Poke failed: ${response.status}${errorText ? ` - ${errorText}` : ""}`);
|
|
61
|
-
}
|
|
62
|
-
return pokeId;
|
|
63
|
-
} finally {
|
|
64
|
-
await release();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function scryUrbitPath(
|
|
69
|
-
deps: Pick<UrbitChannelDeps, "baseUrl" | "cookie" | "ssrfPolicy" | "lookupFn" | "fetchImpl">,
|
|
70
|
-
params: { path: string; auditContext: string },
|
|
71
|
-
): Promise<unknown> {
|
|
72
|
-
const scryPath = `/~/scry${params.path}`;
|
|
73
|
-
const { response, release } = await urbitFetch({
|
|
74
|
-
baseUrl: deps.baseUrl,
|
|
75
|
-
path: scryPath,
|
|
76
|
-
init: {
|
|
77
|
-
method: "GET",
|
|
78
|
-
headers: { Cookie: deps.cookie },
|
|
79
|
-
},
|
|
80
|
-
ssrfPolicy: deps.ssrfPolicy,
|
|
81
|
-
lookupFn: deps.lookupFn,
|
|
82
|
-
fetchImpl: deps.fetchImpl,
|
|
83
|
-
timeoutMs: 30_000,
|
|
84
|
-
auditContext: params.auditContext,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
if (!response.ok) {
|
|
89
|
-
throw new Error(`Scry failed: ${response.status} for path ${params.path}`);
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
return await response.json();
|
|
93
|
-
} catch (cause) {
|
|
94
|
-
throw new Error(`Urbit scry response was malformed JSON for path ${params.path}`, { cause });
|
|
95
|
-
}
|
|
96
|
-
} finally {
|
|
97
|
-
await release();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function createUrbitChannel(
|
|
102
|
-
deps: UrbitChannelDeps,
|
|
103
|
-
params: { body: unknown; auditContext: string },
|
|
104
|
-
): Promise<void> {
|
|
105
|
-
const { response, release } = await putUrbitChannel(deps, params);
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
if (!response.ok && response.status !== 204) {
|
|
109
|
-
throw new UrbitHttpError({ operation: "Channel creation", status: response.status });
|
|
110
|
-
}
|
|
111
|
-
} finally {
|
|
112
|
-
await release();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function wakeUrbitChannel(deps: UrbitChannelDeps): Promise<void> {
|
|
117
|
-
const { response, release } = await putUrbitChannel(deps, {
|
|
118
|
-
body: [
|
|
119
|
-
{
|
|
120
|
-
id: Date.now(),
|
|
121
|
-
action: "poke",
|
|
122
|
-
ship: deps.ship,
|
|
123
|
-
app: "hood",
|
|
124
|
-
mark: "helm-hi",
|
|
125
|
-
json: "Opening API channel",
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
auditContext: "tlon-urbit-channel-wake",
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
if (!response.ok && response.status !== 204) {
|
|
133
|
-
throw new UrbitHttpError({ operation: "Channel activation", status: response.status });
|
|
134
|
-
}
|
|
135
|
-
} finally {
|
|
136
|
-
await release();
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export async function ensureUrbitChannelOpen(
|
|
141
|
-
deps: UrbitChannelDeps,
|
|
142
|
-
params: { createBody: unknown; createAuditContext: string },
|
|
143
|
-
): Promise<void> {
|
|
144
|
-
await createUrbitChannel(deps, {
|
|
145
|
-
body: params.createBody,
|
|
146
|
-
auditContext: params.createAuditContext,
|
|
147
|
-
});
|
|
148
|
-
await wakeUrbitChannel(deps);
|
|
149
|
-
}
|
package/src/urbit/context.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export { ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "klaw/plugin-sdk/ssrf-runtime";
|
|
2
|
-
import { normalizeUrbitHostname, validateUrbitBaseUrl } from "./base-url.js";
|
|
3
|
-
import { UrbitUrlError } from "./errors.js";
|
|
4
|
-
|
|
5
|
-
type UrbitContext = {
|
|
6
|
-
baseUrl: string;
|
|
7
|
-
hostname: string;
|
|
8
|
-
ship: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
function resolveShipFromHostname(hostname: string): string {
|
|
12
|
-
const trimmed = normalizeUrbitHostname(hostname);
|
|
13
|
-
if (!trimmed) {
|
|
14
|
-
return "";
|
|
15
|
-
}
|
|
16
|
-
if (trimmed.includes(".")) {
|
|
17
|
-
return trimmed.split(".")[0] ?? trimmed;
|
|
18
|
-
}
|
|
19
|
-
return trimmed;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function normalizeUrbitShip(ship: string | undefined, hostname: string): string {
|
|
23
|
-
const raw = ship?.replace(/^~/, "") ?? resolveShipFromHostname(hostname);
|
|
24
|
-
return raw.trim();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function normalizeUrbitCookie(cookie: string): string {
|
|
28
|
-
return cookie.split(";")[0] ?? cookie;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function getUrbitContext(url: string, ship?: string): UrbitContext {
|
|
32
|
-
const validated = validateUrbitBaseUrl(url);
|
|
33
|
-
if (!validated.ok) {
|
|
34
|
-
throw new UrbitUrlError(validated.error);
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
baseUrl: validated.baseUrl,
|
|
38
|
-
hostname: validated.hostname,
|
|
39
|
-
ship: normalizeUrbitShip(ship, validated.hostname),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get the default SSRF policy for image uploads.
|
|
45
|
-
* Uses a restrictive policy that blocks private networks by default.
|
|
46
|
-
*/
|
|
47
|
-
export function getDefaultSsrFPolicy(): undefined {
|
|
48
|
-
// Default: block private networks for image uploads (safer default)
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
package/src/urbit/errors.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
type UrbitErrorCode =
|
|
2
|
-
| "invalid_url"
|
|
3
|
-
| "http_error"
|
|
4
|
-
| "auth_failed"
|
|
5
|
-
| "missing_cookie"
|
|
6
|
-
| "channel_not_open";
|
|
7
|
-
|
|
8
|
-
class UrbitError extends Error {
|
|
9
|
-
readonly code: UrbitErrorCode;
|
|
10
|
-
|
|
11
|
-
constructor(code: UrbitErrorCode, message: string, options?: { cause?: unknown }) {
|
|
12
|
-
super(message, options);
|
|
13
|
-
this.name = "UrbitError";
|
|
14
|
-
this.code = code;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class UrbitUrlError extends UrbitError {
|
|
19
|
-
constructor(message: string, options?: { cause?: unknown }) {
|
|
20
|
-
super("invalid_url", message, options);
|
|
21
|
-
this.name = "UrbitUrlError";
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class UrbitHttpError extends UrbitError {
|
|
26
|
-
readonly status: number;
|
|
27
|
-
readonly operation: string;
|
|
28
|
-
readonly bodyText?: string;
|
|
29
|
-
|
|
30
|
-
constructor(params: { operation: string; status: number; bodyText?: string; cause?: unknown }) {
|
|
31
|
-
const suffix = params.bodyText ? ` - ${params.bodyText}` : "";
|
|
32
|
-
super("http_error", `${params.operation} failed: ${params.status}${suffix}`, {
|
|
33
|
-
cause: params.cause,
|
|
34
|
-
});
|
|
35
|
-
this.name = "UrbitHttpError";
|
|
36
|
-
this.status = params.status;
|
|
37
|
-
this.operation = params.operation;
|
|
38
|
-
this.bodyText = params.bodyText;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export class UrbitAuthError extends UrbitError {
|
|
43
|
-
constructor(
|
|
44
|
-
code: "auth_failed" | "missing_cookie",
|
|
45
|
-
message: string,
|
|
46
|
-
options?: { cause?: unknown },
|
|
47
|
-
) {
|
|
48
|
-
super(code, message, options);
|
|
49
|
-
this.name = "UrbitAuthError";
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/urbit/fetch.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { fetchWithSsrFGuard, type LookupFn, type SsrFPolicy } from "klaw/plugin-sdk/ssrf-runtime";
|
|
2
|
-
import { validateUrbitBaseUrl } from "./base-url.js";
|
|
3
|
-
import { UrbitUrlError } from "./errors.js";
|
|
4
|
-
|
|
5
|
-
type UrbitFetchOptions = {
|
|
6
|
-
baseUrl: string;
|
|
7
|
-
path: string;
|
|
8
|
-
init?: RequestInit;
|
|
9
|
-
ssrfPolicy?: SsrFPolicy;
|
|
10
|
-
lookupFn?: LookupFn;
|
|
11
|
-
fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
12
|
-
timeoutMs?: number;
|
|
13
|
-
maxRedirects?: number;
|
|
14
|
-
signal?: AbortSignal;
|
|
15
|
-
auditContext?: string;
|
|
16
|
-
pinDns?: boolean;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export async function urbitFetch(params: UrbitFetchOptions) {
|
|
20
|
-
const validated = validateUrbitBaseUrl(params.baseUrl);
|
|
21
|
-
if (!validated.ok) {
|
|
22
|
-
throw new UrbitUrlError(validated.error);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const url = new URL(params.path, validated.baseUrl).toString();
|
|
26
|
-
return await fetchWithSsrFGuard({
|
|
27
|
-
url,
|
|
28
|
-
fetchImpl: params.fetchImpl,
|
|
29
|
-
init: params.init,
|
|
30
|
-
timeoutMs: params.timeoutMs,
|
|
31
|
-
maxRedirects: params.maxRedirects,
|
|
32
|
-
signal: params.signal,
|
|
33
|
-
policy: params.ssrfPolicy,
|
|
34
|
-
lookupFn: params.lookupFn,
|
|
35
|
-
auditContext: params.auditContext,
|
|
36
|
-
pinDns: params.pinDns,
|
|
37
|
-
});
|
|
38
|
-
}
|
package/src/urbit/foreigns.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types for Urbit groups foreigns (group invites)
|
|
3
|
-
* Based on packages/shared/src/urbit/groups.ts from homestead
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
interface GroupPreviewV7 {
|
|
7
|
-
meta: {
|
|
8
|
-
title: string;
|
|
9
|
-
description: string;
|
|
10
|
-
image: string;
|
|
11
|
-
cover: string;
|
|
12
|
-
};
|
|
13
|
-
"channel-count": number;
|
|
14
|
-
"member-count": number;
|
|
15
|
-
admissions: {
|
|
16
|
-
privacy: "public" | "private" | "secret";
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface ForeignInvite {
|
|
21
|
-
flag: string; // group flag e.g. "~host/group-name"
|
|
22
|
-
time: number; // timestamp
|
|
23
|
-
from: string; // ship that sent invite
|
|
24
|
-
token: string | null;
|
|
25
|
-
note: string | null;
|
|
26
|
-
preview: GroupPreviewV7;
|
|
27
|
-
valid: boolean; // tracks if invite has been revoked
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type Lookup = "preview" | "done" | "error";
|
|
31
|
-
type Progress = "ask" | "join" | "watch" | "done" | "error";
|
|
32
|
-
|
|
33
|
-
interface Foreign {
|
|
34
|
-
invites: ForeignInvite[];
|
|
35
|
-
lookup: Lookup | null;
|
|
36
|
-
preview: GroupPreviewV7 | null;
|
|
37
|
-
progress: Progress | null;
|
|
38
|
-
token: string | null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface Foreigns {
|
|
42
|
-
[flag: string]: Foreign;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// DM invite structure from chat /v3 firehose
|
|
46
|
-
export interface DmInvite {
|
|
47
|
-
ship: string;
|
|
48
|
-
// Additional fields may be present
|
|
49
|
-
}
|
package/src/urbit/send.test.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
vi.mock("@urbit/aura", () => ({
|
|
4
|
-
scot: vi.fn(() => "mocked-ud"),
|
|
5
|
-
da: {
|
|
6
|
-
fromUnix: vi.fn(() => 123n),
|
|
7
|
-
},
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
describe("sendDm", () => {
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
vi.restoreAllMocks();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("uses aura v3 helpers for the DM id", async () => {
|
|
16
|
-
const { sendDm } = await import("./send.js");
|
|
17
|
-
const aura = await import("@urbit/aura");
|
|
18
|
-
const scot = vi.mocked(aura.scot);
|
|
19
|
-
const fromUnix = vi.mocked(aura.da.fromUnix);
|
|
20
|
-
|
|
21
|
-
const sentAt = 1_700_000_000_000;
|
|
22
|
-
vi.spyOn(Date, "now").mockReturnValue(sentAt);
|
|
23
|
-
|
|
24
|
-
const poke = vi.fn(async () => ({}));
|
|
25
|
-
|
|
26
|
-
const result = await sendDm({
|
|
27
|
-
api: { poke },
|
|
28
|
-
fromShip: "~zod",
|
|
29
|
-
toShip: "~nec",
|
|
30
|
-
text: "hi",
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
expect(fromUnix).toHaveBeenCalledWith(sentAt);
|
|
34
|
-
expect(scot).toHaveBeenCalledWith("ud", 123n);
|
|
35
|
-
expect(poke).toHaveBeenCalledTimes(1);
|
|
36
|
-
expect(result.messageId).toBe("~zod/mocked-ud");
|
|
37
|
-
expect(result.receipt.primaryPlatformMessageId).toBe("~zod/mocked-ud");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("passes numeric group reply ids through aura formatting", async () => {
|
|
41
|
-
const { sendGroupMessage } = await import("./send.js");
|
|
42
|
-
const aura = await import("@urbit/aura");
|
|
43
|
-
const scot = vi.mocked(aura.scot);
|
|
44
|
-
scot.mockReturnValueOnce("~2024.1.1");
|
|
45
|
-
vi.spyOn(Date, "now").mockReturnValue(1_700_000_000_000);
|
|
46
|
-
const poke = vi.fn(async () => ({}));
|
|
47
|
-
|
|
48
|
-
const result = await sendGroupMessage({
|
|
49
|
-
api: { poke },
|
|
50
|
-
fromShip: "~zod",
|
|
51
|
-
hostShip: "~nec",
|
|
52
|
-
channelName: "general",
|
|
53
|
-
text: "threaded",
|
|
54
|
-
replyToId: "1700000000000",
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
expect(scot).toHaveBeenCalledWith("ud", 1_700_000_000_000n);
|
|
58
|
-
expect(poke).toHaveBeenCalledWith({
|
|
59
|
-
app: "channels",
|
|
60
|
-
mark: "channel-action-1",
|
|
61
|
-
json: {
|
|
62
|
-
channel: {
|
|
63
|
-
nest: "chat/~nec/general",
|
|
64
|
-
action: {
|
|
65
|
-
post: {
|
|
66
|
-
reply: {
|
|
67
|
-
id: "~2024.1.1",
|
|
68
|
-
action: {
|
|
69
|
-
add: {
|
|
70
|
-
content: [{ inline: ["threaded"] }],
|
|
71
|
-
author: "~zod",
|
|
72
|
-
sent: 1_700_000_000_000,
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
expect(result.receipt.threadId).toBe("~nec/general");
|
|
82
|
-
});
|
|
83
|
-
});
|