refactor!: rename chat providers to channels
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { requirePairingAdapter } from "../providers/plugins/pairing.js";
|
||||
import type { PairingProvider } from "./pairing-store.js";
|
||||
import { requirePairingAdapter } from "../channels/plugins/pairing.js";
|
||||
import type { PairingChannel } from "./pairing-store.js";
|
||||
|
||||
export function resolvePairingIdLabel(provider: PairingProvider): string {
|
||||
return requirePairingAdapter(provider).idLabel;
|
||||
export function resolvePairingIdLabel(channel: PairingChannel): string {
|
||||
return requirePairingAdapter(channel).idLabel;
|
||||
}
|
||||
|
||||
@@ -5,39 +5,39 @@ import { buildPairingReply } from "./pairing-messages.js";
|
||||
describe("buildPairingReply", () => {
|
||||
const cases = [
|
||||
{
|
||||
provider: "discord",
|
||||
channel: "discord",
|
||||
idLine: "Your Discord user id: 1",
|
||||
code: "ABC123",
|
||||
},
|
||||
{
|
||||
provider: "slack",
|
||||
channel: "slack",
|
||||
idLine: "Your Slack user id: U1",
|
||||
code: "DEF456",
|
||||
},
|
||||
{
|
||||
provider: "signal",
|
||||
channel: "signal",
|
||||
idLine: "Your Signal number: +15550001111",
|
||||
code: "GHI789",
|
||||
},
|
||||
{
|
||||
provider: "imessage",
|
||||
channel: "imessage",
|
||||
idLine: "Your iMessage sender id: +15550002222",
|
||||
code: "JKL012",
|
||||
},
|
||||
{
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
idLine: "Your WhatsApp phone number: +15550003333",
|
||||
code: "MNO345",
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
it(`formats pairing reply for ${testCase.provider}`, () => {
|
||||
it(`formats pairing reply for ${testCase.channel}`, () => {
|
||||
const text = buildPairingReply(testCase);
|
||||
expect(text).toContain(testCase.idLine);
|
||||
expect(text).toContain(`Pairing code: ${testCase.code}`);
|
||||
expect(text).toContain(
|
||||
`clawdbot pairing approve ${testCase.provider} <code>`,
|
||||
`clawdbot pairing approve ${testCase.channel} <code>`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { PairingProvider } from "./pairing-store.js";
|
||||
import type { PairingChannel } from "./pairing-store.js";
|
||||
|
||||
export function buildPairingReply(params: {
|
||||
provider: PairingProvider;
|
||||
channel: PairingChannel;
|
||||
idLine: string;
|
||||
code: string;
|
||||
}): string {
|
||||
const { provider, idLine, code } = params;
|
||||
const { channel, idLine, code } = params;
|
||||
return [
|
||||
"Clawdbot: access not configured.",
|
||||
"",
|
||||
@@ -14,6 +14,6 @@ export function buildPairingReply(params: {
|
||||
`Pairing code: ${code}`,
|
||||
"",
|
||||
"Ask the bot owner to approve with:",
|
||||
`clawdbot pairing approve ${provider} <code>`,
|
||||
`clawdbot pairing approve ${channel} <code>`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { resolveOAuthDir } from "../config/paths.js";
|
||||
import {
|
||||
listProviderPairingRequests,
|
||||
upsertProviderPairingRequest,
|
||||
listChannelPairingRequests,
|
||||
upsertChannelPairingRequest,
|
||||
} from "./pairing-store.js";
|
||||
|
||||
async function withTempStateDir<T>(fn: (stateDir: string) => Promise<T>) {
|
||||
@@ -27,19 +27,19 @@ async function withTempStateDir<T>(fn: (stateDir: string) => Promise<T>) {
|
||||
describe("pairing store", () => {
|
||||
it("reuses pending code and reports created=false", async () => {
|
||||
await withTempStateDir(async () => {
|
||||
const first = await upsertProviderPairingRequest({
|
||||
provider: "discord",
|
||||
const first = await upsertChannelPairingRequest({
|
||||
channel: "discord",
|
||||
id: "u1",
|
||||
});
|
||||
const second = await upsertProviderPairingRequest({
|
||||
provider: "discord",
|
||||
const second = await upsertChannelPairingRequest({
|
||||
channel: "discord",
|
||||
id: "u1",
|
||||
});
|
||||
expect(first.created).toBe(true);
|
||||
expect(second.created).toBe(false);
|
||||
expect(second.code).toBe(first.code);
|
||||
|
||||
const list = await listProviderPairingRequests("discord");
|
||||
const list = await listChannelPairingRequests("discord");
|
||||
expect(list).toHaveLength(1);
|
||||
expect(list[0]?.code).toBe(first.code);
|
||||
});
|
||||
@@ -47,8 +47,8 @@ describe("pairing store", () => {
|
||||
|
||||
it("expires pending requests after TTL", async () => {
|
||||
await withTempStateDir(async (stateDir) => {
|
||||
const created = await upsertProviderPairingRequest({
|
||||
provider: "signal",
|
||||
const created = await upsertChannelPairingRequest({
|
||||
channel: "signal",
|
||||
id: "+15550001111",
|
||||
});
|
||||
expect(created.created).toBe(true);
|
||||
@@ -71,11 +71,11 @@ describe("pairing store", () => {
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const list = await listProviderPairingRequests("signal");
|
||||
const list = await listChannelPairingRequests("signal");
|
||||
expect(list).toHaveLength(0);
|
||||
|
||||
const next = await upsertProviderPairingRequest({
|
||||
provider: "signal",
|
||||
const next = await upsertChannelPairingRequest({
|
||||
channel: "signal",
|
||||
id: "+15550001111",
|
||||
});
|
||||
expect(next.created).toBe(true);
|
||||
@@ -87,8 +87,8 @@ describe("pairing store", () => {
|
||||
const spy = vi.spyOn(crypto, "randomInt");
|
||||
try {
|
||||
spy.mockReturnValue(0);
|
||||
const first = await upsertProviderPairingRequest({
|
||||
provider: "telegram",
|
||||
const first = await upsertChannelPairingRequest({
|
||||
channel: "telegram",
|
||||
id: "123",
|
||||
});
|
||||
expect(first.code).toBe("AAAAAAAA");
|
||||
@@ -96,8 +96,8 @@ describe("pairing store", () => {
|
||||
const sequence = Array(8).fill(0).concat(Array(8).fill(1));
|
||||
let idx = 0;
|
||||
spy.mockImplementation(() => sequence[idx++] ?? 1);
|
||||
const second = await upsertProviderPairingRequest({
|
||||
provider: "telegram",
|
||||
const second = await upsertChannelPairingRequest({
|
||||
channel: "telegram",
|
||||
id: "456",
|
||||
});
|
||||
expect(second.code).toBe("BBBBBBBB");
|
||||
@@ -111,20 +111,20 @@ describe("pairing store", () => {
|
||||
await withTempStateDir(async () => {
|
||||
const ids = ["+15550000001", "+15550000002", "+15550000003"];
|
||||
for (const id of ids) {
|
||||
const created = await upsertProviderPairingRequest({
|
||||
provider: "whatsapp",
|
||||
const created = await upsertChannelPairingRequest({
|
||||
channel: "whatsapp",
|
||||
id,
|
||||
});
|
||||
expect(created.created).toBe(true);
|
||||
}
|
||||
|
||||
const blocked = await upsertProviderPairingRequest({
|
||||
provider: "whatsapp",
|
||||
const blocked = await upsertChannelPairingRequest({
|
||||
channel: "whatsapp",
|
||||
id: "+15550000004",
|
||||
});
|
||||
expect(blocked.created).toBe(false);
|
||||
|
||||
const list = await listProviderPairingRequests("whatsapp");
|
||||
const list = await listChannelPairingRequests("whatsapp");
|
||||
const listIds = list.map((entry) => entry.id);
|
||||
expect(listIds).toHaveLength(3);
|
||||
expect(listIds).toContain("+15550000001");
|
||||
|
||||
@@ -4,10 +4,9 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import lockfile from "proper-lockfile";
|
||||
|
||||
import { requirePairingAdapter } from "../channels/plugins/pairing.js";
|
||||
import type { ChannelId } from "../channels/plugins/types.js";
|
||||
import { resolveOAuthDir, resolveStateDir } from "../config/paths.js";
|
||||
import { requirePairingAdapter } from "../providers/plugins/pairing.js";
|
||||
import type { ProviderId } from "../providers/plugins/types.js";
|
||||
|
||||
const PAIRING_CODE_LENGTH = 8;
|
||||
const PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
@@ -24,7 +23,7 @@ const PAIRING_STORE_LOCK_OPTIONS = {
|
||||
stale: 30_000,
|
||||
} as const;
|
||||
|
||||
export type PairingProvider = ProviderId;
|
||||
export type PairingChannel = ChannelId;
|
||||
|
||||
export type PairingRequest = {
|
||||
id: string;
|
||||
@@ -50,17 +49,17 @@ function resolveCredentialsDir(env: NodeJS.ProcessEnv = process.env): string {
|
||||
}
|
||||
|
||||
function resolvePairingPath(
|
||||
provider: PairingProvider,
|
||||
channel: PairingChannel,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): string {
|
||||
return path.join(resolveCredentialsDir(env), `${provider}-pairing.json`);
|
||||
return path.join(resolveCredentialsDir(env), `${channel}-pairing.json`);
|
||||
}
|
||||
|
||||
function resolveAllowFromPath(
|
||||
provider: PairingProvider,
|
||||
channel: PairingChannel,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): string {
|
||||
return path.join(resolveCredentialsDir(env), `${provider}-allowFrom.json`);
|
||||
return path.join(resolveCredentialsDir(env), `${channel}-allowFrom.json`);
|
||||
}
|
||||
|
||||
function safeParseJson<T>(raw: string): T | null {
|
||||
@@ -194,8 +193,8 @@ function normalizeId(value: string | number): string {
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
function normalizeAllowEntry(provider: PairingProvider, entry: string): string {
|
||||
const adapter = requirePairingAdapter(provider);
|
||||
function normalizeAllowEntry(channel: PairingChannel, entry: string): string {
|
||||
const adapter = requirePairingAdapter(channel);
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) return "";
|
||||
if (trimmed === "*") return "";
|
||||
@@ -205,30 +204,30 @@ function normalizeAllowEntry(provider: PairingProvider, entry: string): string {
|
||||
return String(normalized).trim();
|
||||
}
|
||||
|
||||
export async function readProviderAllowFromStore(
|
||||
provider: PairingProvider,
|
||||
export async function readChannelAllowFromStore(
|
||||
channel: PairingChannel,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): Promise<string[]> {
|
||||
requirePairingAdapter(provider);
|
||||
const filePath = resolveAllowFromPath(provider, env);
|
||||
requirePairingAdapter(channel);
|
||||
const filePath = resolveAllowFromPath(channel, env);
|
||||
const { value } = await readJsonFile<AllowFromStore>(filePath, {
|
||||
version: 1,
|
||||
allowFrom: [],
|
||||
});
|
||||
const list = Array.isArray(value.allowFrom) ? value.allowFrom : [];
|
||||
return list
|
||||
.map((v) => normalizeAllowEntry(provider, String(v)))
|
||||
.map((v) => normalizeAllowEntry(channel, String(v)))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export async function addProviderAllowFromStoreEntry(params: {
|
||||
provider: PairingProvider;
|
||||
export async function addChannelAllowFromStoreEntry(params: {
|
||||
channel: PairingChannel;
|
||||
entry: string | number;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<{ changed: boolean; allowFrom: string[] }> {
|
||||
requirePairingAdapter(params.provider);
|
||||
requirePairingAdapter(params.channel);
|
||||
const env = params.env ?? process.env;
|
||||
const filePath = resolveAllowFromPath(params.provider, env);
|
||||
const filePath = resolveAllowFromPath(params.channel, env);
|
||||
return await withFileLock(
|
||||
filePath,
|
||||
{ version: 1, allowFrom: [] } satisfies AllowFromStore,
|
||||
@@ -238,10 +237,10 @@ export async function addProviderAllowFromStoreEntry(params: {
|
||||
allowFrom: [],
|
||||
});
|
||||
const current = (Array.isArray(value.allowFrom) ? value.allowFrom : [])
|
||||
.map((v) => normalizeAllowEntry(params.provider, String(v)))
|
||||
.map((v) => normalizeAllowEntry(params.channel, String(v)))
|
||||
.filter(Boolean);
|
||||
const normalized = normalizeAllowEntry(
|
||||
params.provider,
|
||||
params.channel,
|
||||
normalizeId(params.entry),
|
||||
);
|
||||
if (!normalized) return { changed: false, allowFrom: current };
|
||||
@@ -257,12 +256,12 @@ export async function addProviderAllowFromStoreEntry(params: {
|
||||
);
|
||||
}
|
||||
|
||||
export async function listProviderPairingRequests(
|
||||
provider: PairingProvider,
|
||||
export async function listChannelPairingRequests(
|
||||
channel: PairingChannel,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): Promise<PairingRequest[]> {
|
||||
requirePairingAdapter(provider);
|
||||
const filePath = resolvePairingPath(provider, env);
|
||||
requirePairingAdapter(channel);
|
||||
const filePath = resolvePairingPath(channel, env);
|
||||
return await withFileLock(
|
||||
filePath,
|
||||
{ version: 1, requests: [] } satisfies PairingStore,
|
||||
@@ -299,15 +298,15 @@ export async function listProviderPairingRequests(
|
||||
);
|
||||
}
|
||||
|
||||
export async function upsertProviderPairingRequest(params: {
|
||||
provider: PairingProvider;
|
||||
export async function upsertChannelPairingRequest(params: {
|
||||
channel: PairingChannel;
|
||||
id: string | number;
|
||||
meta?: Record<string, string | undefined | null>;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<{ code: string; created: boolean }> {
|
||||
requirePairingAdapter(params.provider);
|
||||
requirePairingAdapter(params.channel);
|
||||
const env = params.env ?? process.env;
|
||||
const filePath = resolvePairingPath(params.provider, env);
|
||||
const filePath = resolvePairingPath(params.channel, env);
|
||||
return await withFileLock(
|
||||
filePath,
|
||||
{ version: 1, requests: [] } satisfies PairingStore,
|
||||
@@ -398,17 +397,17 @@ export async function upsertProviderPairingRequest(params: {
|
||||
);
|
||||
}
|
||||
|
||||
export async function approveProviderPairingCode(params: {
|
||||
provider: PairingProvider;
|
||||
export async function approveChannelPairingCode(params: {
|
||||
channel: PairingChannel;
|
||||
code: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<{ id: string; entry?: PairingRequest } | null> {
|
||||
requirePairingAdapter(params.provider);
|
||||
requirePairingAdapter(params.channel);
|
||||
const env = params.env ?? process.env;
|
||||
const code = params.code.trim().toUpperCase();
|
||||
if (!code) return null;
|
||||
|
||||
const filePath = resolvePairingPath(params.provider, env);
|
||||
const filePath = resolvePairingPath(params.channel, env);
|
||||
return await withFileLock(
|
||||
filePath,
|
||||
{ version: 1, requests: [] } satisfies PairingStore,
|
||||
@@ -439,8 +438,8 @@ export async function approveProviderPairingCode(params: {
|
||||
version: 1,
|
||||
requests: pruned,
|
||||
} satisfies PairingStore);
|
||||
await addProviderAllowFromStoreEntry({
|
||||
provider: params.provider,
|
||||
await addChannelAllowFromStoreEntry({
|
||||
channel: params.channel,
|
||||
entry: entry.id,
|
||||
env,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user