Converted go-roots-ws to typescript.
This commit is contained in:
262
src/envelope/enclose.test.ts
Normal file
262
src/envelope/enclose.test.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
encloseAuthChallenge,
|
||||
encloseAuthResponse,
|
||||
encloseClose,
|
||||
encloseClosed,
|
||||
encloseEOSE,
|
||||
encloseEvent,
|
||||
encloseNotice,
|
||||
encloseOK,
|
||||
encloseReq,
|
||||
encloseSubscriptionEvent,
|
||||
} from "./enclose";
|
||||
|
||||
describe("encloseEvent", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "empty event",
|
||||
event: "{}",
|
||||
want: '["EVENT",{}]',
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
event: "in[valid,]",
|
||||
want: '["EVENT",in[valid,]]',
|
||||
},
|
||||
{
|
||||
name: "populated event",
|
||||
event: '{"id":"abc123","kind":1,"sig":"abc123"}',
|
||||
want: '["EVENT",{"id":"abc123","kind":1,"sig":"abc123"}]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseEvent(tc.event);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseOK", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "successful event",
|
||||
eventID: "abc123",
|
||||
status: true,
|
||||
message: "Event accepted",
|
||||
want: '["OK","abc123",true,"Event accepted"]',
|
||||
},
|
||||
{
|
||||
name: "rejected event",
|
||||
eventID: "xyz789",
|
||||
status: false,
|
||||
message: "Invalid signature",
|
||||
want: '["OK","xyz789",false,"Invalid signature"]',
|
||||
},
|
||||
{
|
||||
name: "empty message",
|
||||
eventID: "def456",
|
||||
status: true,
|
||||
message: "",
|
||||
want: '["OK","def456",true,""]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseOK(tc.eventID, tc.status, tc.message);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseReq", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "single filter",
|
||||
subID: "sub1",
|
||||
filters: ['{"kinds":[1],"limit":10}'],
|
||||
want: '["REQ","sub1",{"kinds":[1],"limit":10}]',
|
||||
},
|
||||
{
|
||||
name: "multiple filters",
|
||||
subID: "sub2",
|
||||
filters: ['{"kinds":[1]}', '{"authors":["abc"]}'],
|
||||
want: '["REQ","sub2",{"kinds":[1]},{"authors":["abc"]}]',
|
||||
},
|
||||
{
|
||||
name: "no filters",
|
||||
subID: "sub3",
|
||||
filters: [],
|
||||
want: '["REQ","sub3"]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseReq(tc.subID, tc.filters);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseSubscriptionEvent", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "basic event",
|
||||
subID: "sub1",
|
||||
event: '{"id":"abc123","kind":1}',
|
||||
want: '["EVENT","sub1",{"id":"abc123","kind":1}]',
|
||||
},
|
||||
{
|
||||
name: "empty event",
|
||||
subID: "sub2",
|
||||
event: "{}",
|
||||
want: '["EVENT","sub2",{}]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseSubscriptionEvent(tc.subID, tc.event);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseEOSE", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid subscription ID",
|
||||
subID: "sub1",
|
||||
want: '["EOSE","sub1"]',
|
||||
},
|
||||
{
|
||||
name: "empty subscription ID",
|
||||
subID: "",
|
||||
want: '["EOSE",""]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseEOSE(tc.subID);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseClose", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid subscription ID",
|
||||
subID: "sub1",
|
||||
want: '["CLOSE","sub1"]',
|
||||
},
|
||||
{
|
||||
name: "empty subscription ID",
|
||||
subID: "",
|
||||
want: '["CLOSE",""]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseClose(tc.subID);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseClosed", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "with message",
|
||||
subID: "sub1",
|
||||
message: "Subscription complete",
|
||||
want: '["CLOSED","sub1","Subscription complete"]',
|
||||
},
|
||||
{
|
||||
name: "empty message",
|
||||
subID: "sub2",
|
||||
message: "",
|
||||
want: '["CLOSED","sub2",""]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseClosed(tc.subID, tc.message);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseNotice", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid message",
|
||||
message: "This is a notice",
|
||||
want: '["NOTICE","This is a notice"]',
|
||||
},
|
||||
{
|
||||
name: "empty message",
|
||||
message: "",
|
||||
want: '["NOTICE",""]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseNotice(tc.message);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseAuthChallenge", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid challenge",
|
||||
challenge: "random-challenge-string",
|
||||
want: '["AUTH","random-challenge-string"]',
|
||||
},
|
||||
{
|
||||
name: "empty challenge",
|
||||
challenge: "",
|
||||
want: '["AUTH",""]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseAuthChallenge(tc.challenge);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encloseAuthResponse", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid event",
|
||||
event: '{"id":"abc123","kind":22242}',
|
||||
want: '["AUTH",{"id":"abc123","kind":22242}]',
|
||||
},
|
||||
{
|
||||
name: "empty event",
|
||||
event: "{}",
|
||||
want: '["AUTH",{}]',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
const got = encloseAuthResponse(tc.event);
|
||||
expect(got).toEqual(tc.want);
|
||||
});
|
||||
});
|
||||
});
|
||||
95
src/envelope/enclose.ts
Normal file
95
src/envelope/enclose.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Envelope } from "../types";
|
||||
|
||||
/**
|
||||
* Creates an EVENT envelope for publishing events.
|
||||
* It wraps the provided event JSON in the format ["EVENT", event].
|
||||
*/
|
||||
export function encloseEvent(event: string): Envelope {
|
||||
return `["EVENT",${event}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an OK envelope acknowledging receipt of an event.
|
||||
* Format: ["OK", eventID, status, message]
|
||||
*/
|
||||
export function encloseOK(
|
||||
eventID: string,
|
||||
status: boolean,
|
||||
message: string,
|
||||
): Envelope {
|
||||
return `["OK","${eventID}",${status},"${message}"]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a REQ envelope for subscription requests.
|
||||
* Format: ["REQ", subID, filter1, filter2, ...]
|
||||
*/
|
||||
export function encloseReq(subID: string, filters: string[]): Envelope {
|
||||
let envelope = `["REQ","${subID}"`;
|
||||
|
||||
for (const filter of filters) {
|
||||
envelope += `,${filter}`;
|
||||
}
|
||||
|
||||
envelope += "]";
|
||||
return envelope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an EVENT envelope for delivering subscription events.
|
||||
* Format: ["EVENT", subID, event]
|
||||
*/
|
||||
export function encloseSubscriptionEvent(
|
||||
subID: string,
|
||||
event: string,
|
||||
): Envelope {
|
||||
return `["EVENT","${subID}",${event}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an EOSE (End of Stored Events) envelope.
|
||||
* Format: ["EOSE", subID]
|
||||
*/
|
||||
export function encloseEOSE(subID: string): Envelope {
|
||||
return `["EOSE","${subID}"]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CLOSE envelope for ending a subscription.
|
||||
* Format: ["CLOSE", subID]
|
||||
*/
|
||||
export function encloseClose(subID: string): Envelope {
|
||||
return `["CLOSE","${subID}"]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CLOSED envelope for indicating a terminated subscription.
|
||||
* Format: ["CLOSED", subID, message]
|
||||
*/
|
||||
export function encloseClosed(subID: string, message: string): Envelope {
|
||||
return `["CLOSED","${subID}","${message}"]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a NOTICE envelope for responder messages.
|
||||
* Format: ["NOTICE", message]
|
||||
*/
|
||||
export function encloseNotice(message: string): Envelope {
|
||||
return `["NOTICE","${message}"]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AUTH challenge envelope.
|
||||
* Format: ["AUTH", challenge]
|
||||
*/
|
||||
export function encloseAuthChallenge(challenge: string): Envelope {
|
||||
return `["AUTH","${challenge}"]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AUTH response envelope.
|
||||
* Format: ["AUTH", event]
|
||||
*/
|
||||
export function encloseAuthResponse(event: string): Envelope {
|
||||
return `["AUTH",${event}]`;
|
||||
}
|
||||
101
src/envelope/envelope.test.ts
Normal file
101
src/envelope/envelope.test.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getLabel, getStandardLabels, isStandardLabel } from "./envelope";
|
||||
|
||||
describe("getLabel", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid envelope with EVENT label",
|
||||
env: '["EVENT",{"id":"abc123"}]',
|
||||
wantLabel: "EVENT",
|
||||
},
|
||||
{
|
||||
name: "valid envelope with custom label",
|
||||
env: '["TEST",{"data":"value"}]',
|
||||
wantLabel: "TEST",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "empty array",
|
||||
env: "[]",
|
||||
wantErrText: "empty envelope",
|
||||
},
|
||||
{
|
||||
name: "label not a string",
|
||||
env: '[123,{"id":"abc123"}]',
|
||||
wantErrText: "label is not a string",
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
getLabel(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const got = getLabel(tc.env);
|
||||
expect(got).toEqual(tc.wantLabel);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getStandardLabels", () => {
|
||||
it("returns the correct standard labels", () => {
|
||||
const expected = new Set([
|
||||
"EVENT",
|
||||
"REQ",
|
||||
"CLOSE",
|
||||
"CLOSED",
|
||||
"EOSE",
|
||||
"NOTICE",
|
||||
"OK",
|
||||
"AUTH",
|
||||
]);
|
||||
|
||||
const labels = getStandardLabels();
|
||||
|
||||
// Check that we have the exact same number of labels
|
||||
expect(labels.size).toBe(expected.size);
|
||||
|
||||
// Check that all expected labels are present
|
||||
expected.forEach((label) => {
|
||||
expect(labels.has(label)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("isStandardLabel", () => {
|
||||
const standardCases = [
|
||||
"EVENT",
|
||||
"REQ",
|
||||
"CLOSE",
|
||||
"CLOSED",
|
||||
"EOSE",
|
||||
"NOTICE",
|
||||
"OK",
|
||||
"AUTH",
|
||||
];
|
||||
|
||||
const nonStandardCases = ["TEST", "CUSTOM", "event", "REQ1", ""];
|
||||
|
||||
standardCases.forEach((label) => {
|
||||
it(`${label} should be standard`, () => {
|
||||
expect(isStandardLabel(label)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
nonStandardCases.forEach((label) => {
|
||||
it(`${label} should not be standard`, () => {
|
||||
expect(isStandardLabel(label)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
52
src/envelope/envelope.ts
Normal file
52
src/envelope/envelope.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
InvalidEnvelopeError,
|
||||
InvalidJSONError,
|
||||
WrongFieldTypeError,
|
||||
} from "../errors";
|
||||
import { Envelope } from "../types";
|
||||
|
||||
/**
|
||||
* Gets the label from an envelope.
|
||||
*/
|
||||
export function getLabel(env: Envelope): string {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
if (!Array.isArray(arr) || arr.length < 1) {
|
||||
throw new InvalidEnvelopeError("empty envelope");
|
||||
}
|
||||
|
||||
if (typeof arr[0] !== "string") {
|
||||
throw new WrongFieldTypeError("label is not a string");
|
||||
}
|
||||
|
||||
return arr[0];
|
||||
} catch (err) {
|
||||
if (err instanceof Error && !(err instanceof InvalidJSONError)) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of standard Nostr WebSocket message labels.
|
||||
*/
|
||||
export function getStandardLabels(): Set<string> {
|
||||
return new Set([
|
||||
"EVENT",
|
||||
"REQ",
|
||||
"CLOSE",
|
||||
"CLOSED",
|
||||
"EOSE",
|
||||
"NOTICE",
|
||||
"OK",
|
||||
"AUTH",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given label is a standard Nostr WebSocket message label.
|
||||
*/
|
||||
export function isStandardLabel(label: string): boolean {
|
||||
return getStandardLabels().has(label);
|
||||
}
|
||||
509
src/envelope/find.test.ts
Normal file
509
src/envelope/find.test.ts
Normal file
@@ -0,0 +1,509 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
findAuthChallenge,
|
||||
findAuthResponse,
|
||||
findClose,
|
||||
findClosed,
|
||||
findEOSE,
|
||||
findEvent,
|
||||
findNotice,
|
||||
findOK,
|
||||
findReq,
|
||||
findSubscriptionEvent,
|
||||
} from "./find";
|
||||
|
||||
describe("findEvent", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid event",
|
||||
env: '["EVENT",{"id":"abc123","kind":1}]',
|
||||
wantEvent: '{"id":"abc123","kind":1}',
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["REQ",{"id":"abc123","kind":1}]',
|
||||
wantErrText: "expected EVENT, got REQ",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["EVENT"]',
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["EVENT",{"id":"abc123"},"extra"]',
|
||||
wantEvent: '{"id":"abc123"}',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findEvent(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const got = findEvent(tc.env);
|
||||
expect(JSON.stringify(got)).toEqual(tc.wantEvent);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findSubscriptionEvent", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid event",
|
||||
env: '["EVENT","sub1",{"id":"abc123","kind":1}]',
|
||||
wantSubID: "sub1",
|
||||
wantEvent: '{"id":"abc123","kind":1}',
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["REQ","sub1",{"id":"abc123","kind":1}]',
|
||||
wantErrText: "expected EVENT, got REQ",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["EVENT","sub1"]',
|
||||
wantErrText: "expected 3 elements, got 2",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["EVENT","sub1",{"id":"abc123"},"extra"]',
|
||||
wantSubID: "sub1",
|
||||
wantEvent: '{"id":"abc123"}',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findSubscriptionEvent(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const [gotSubID, gotEvent] = findSubscriptionEvent(tc.env);
|
||||
expect(gotSubID).toEqual(tc.wantSubID);
|
||||
expect(JSON.stringify(gotEvent)).toEqual(tc.wantEvent);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findOK", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "accepted event",
|
||||
env: '["OK","abc123",true,"Event accepted"]',
|
||||
wantEventID: "abc123",
|
||||
wantStatus: true,
|
||||
wantMessage: "Event accepted",
|
||||
},
|
||||
{
|
||||
name: "rejected event",
|
||||
env: '["OK","xyz789",false,"Invalid signature"]',
|
||||
wantEventID: "xyz789",
|
||||
wantStatus: false,
|
||||
wantMessage: "Invalid signature",
|
||||
},
|
||||
{
|
||||
name: "wrong status type",
|
||||
env: '["OK","abc123","ok","Event accepted"]',
|
||||
wantErrText: "status is not the expected type",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["EVENT","abc123",true,"Event accepted"]',
|
||||
wantErrText: "expected OK, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["OK","abc123",true]',
|
||||
wantErrText: "expected 4 elements, got 3",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["OK","abc123",true,"Event accepted","extra"]',
|
||||
wantEventID: "abc123",
|
||||
wantStatus: true,
|
||||
wantMessage: "Event accepted",
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findOK(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const [gotEventID, gotStatus, gotMessage] = findOK(tc.env);
|
||||
expect(gotEventID).toEqual(tc.wantEventID);
|
||||
expect(gotStatus).toEqual(tc.wantStatus);
|
||||
expect(gotMessage).toEqual(tc.wantMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findReq", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "single filter",
|
||||
env: '["REQ","sub1",{"kinds":[1],"limit":10}]',
|
||||
wantSubID: "sub1",
|
||||
wantFilters: ['{"kinds":[1],"limit":10}'],
|
||||
},
|
||||
{
|
||||
name: "multiple filters",
|
||||
env: '["REQ","sub2",{"kinds":[1]},{"authors":["abc"]}]',
|
||||
wantSubID: "sub2",
|
||||
wantFilters: ['{"kinds":[1]}', '{"authors":["abc"]}'],
|
||||
},
|
||||
{
|
||||
name: "no filters",
|
||||
env: '["REQ","sub3"]',
|
||||
wantSubID: "sub3",
|
||||
wantFilters: [],
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["EVENT","sub1",{"kinds":[1],"limit":10}]',
|
||||
wantErrText: "expected REQ, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["REQ"]',
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findReq(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const [gotSubID, gotFilters] = findReq(tc.env);
|
||||
expect(gotSubID).toEqual(tc.wantSubID);
|
||||
expect(gotFilters.map((f) => JSON.stringify(f))).toEqual(
|
||||
tc.wantFilters,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findEOSE", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid EOSE",
|
||||
env: '["EOSE","sub1"]',
|
||||
wantSubID: "sub1",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["EVENT","sub1"]',
|
||||
wantErrText: "expected EOSE, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["EOSE"]',
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["EOSE","sub1","extra"]',
|
||||
wantSubID: "sub1",
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findEOSE(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const gotSubID = findEOSE(tc.env);
|
||||
expect(gotSubID).toEqual(tc.wantSubID);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findClose", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid CLOSE",
|
||||
env: '["CLOSE","sub1"]',
|
||||
wantSubID: "sub1",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["EVENT","sub1"]',
|
||||
wantErrText: "expected CLOSE, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["CLOSE"]',
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["CLOSE","sub1","extra"]',
|
||||
wantSubID: "sub1",
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findClose(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const gotSubID = findClose(tc.env);
|
||||
expect(gotSubID).toEqual(tc.wantSubID);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findClosed", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid CLOSED",
|
||||
env: '["CLOSED","sub1","Subscription complete"]',
|
||||
wantSubID: "sub1",
|
||||
wantMessage: "Subscription complete",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["EVENT","sub1","Subscription complete"]',
|
||||
wantErrText: "expected CLOSED, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["CLOSED","sub1"]',
|
||||
wantErrText: "expected 3 elements, got 2",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["CLOSED","sub1","Subscription complete","extra"]',
|
||||
wantSubID: "sub1",
|
||||
wantMessage: "Subscription complete",
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findClosed(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const [gotSubID, gotMessage] = findClosed(tc.env);
|
||||
expect(gotSubID).toEqual(tc.wantSubID);
|
||||
expect(gotMessage).toEqual(tc.wantMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findNotice", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid NOTICE",
|
||||
env: '["NOTICE","This is a notice"]',
|
||||
wantMessage: "This is a notice",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["EVENT","This is a notice"]',
|
||||
wantErrText: "expected NOTICE, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["NOTICE"]',
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["NOTICE","This is a notice","extra"]',
|
||||
wantMessage: "This is a notice",
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findNotice(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const gotMessage = findNotice(tc.env);
|
||||
expect(gotMessage).toEqual(tc.wantMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findAuthChallenge", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid AUTH challenge",
|
||||
env: '["AUTH","random-challenge-string"]',
|
||||
wantChallenge: "random-challenge-string",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["EVENT","random-challenge-string"]',
|
||||
wantErrText: "expected AUTH, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["AUTH"]',
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["AUTH","random-challenge-string","extra"]',
|
||||
wantChallenge: "random-challenge-string",
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findAuthChallenge(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const gotChallenge = findAuthChallenge(tc.env);
|
||||
expect(gotChallenge).toEqual(tc.wantChallenge);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findAuthResponse", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "valid AUTH response",
|
||||
env: '["AUTH",{"id":"abc123","kind":22242}]',
|
||||
wantEvent: '{"id":"abc123","kind":22242}',
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: '["EVENT",{"id":"abc123","kind":22242}]',
|
||||
wantErrText: "expected AUTH, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: "invalid",
|
||||
wantErrText: "Unexpected token",
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: '["AUTH"]',
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: '["AUTH",{"id":"abc123","kind":22242},"extra"]',
|
||||
wantEvent: '{"id":"abc123","kind":22242}',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, () => {
|
||||
if (tc.wantErrText) {
|
||||
try {
|
||||
findAuthResponse(tc.env);
|
||||
expect.fail("Expected function to throw an error");
|
||||
} catch (err) {
|
||||
expect((err as Error).message).toContain(tc.wantErrText);
|
||||
}
|
||||
} else {
|
||||
const gotEvent = findAuthResponse(tc.env);
|
||||
expect(JSON.stringify(gotEvent)).toEqual(tc.wantEvent);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
332
src/envelope/find.ts
Normal file
332
src/envelope/find.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
import {
|
||||
InvalidEnvelopeError,
|
||||
InvalidJSONError,
|
||||
WrongEnvelopeLabelError,
|
||||
WrongFieldTypeError,
|
||||
} from "../errors";
|
||||
import { Envelope } from "../types";
|
||||
|
||||
/**
|
||||
* Helper function that ensures the JSON array has at least the minimum length required.
|
||||
*/
|
||||
export function checkArrayLength(arr: any[], minLen: number): void {
|
||||
if (arr.length < minLen) {
|
||||
throw new InvalidEnvelopeError(
|
||||
`expected ${minLen} elements, got ${arr.length}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that verifies that the envelope label matches the expected one.
|
||||
*/
|
||||
export function checkLabel(got: string, want: string): void {
|
||||
if (got !== want) {
|
||||
throw new WrongEnvelopeLabelError(`expected ${want}, got ${got}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an event from an EVENT envelope with no subscription ID.
|
||||
* Expected Format: ["EVENT", event]
|
||||
*/
|
||||
export function findEvent(env: Envelope): any {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 2);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "EVENT");
|
||||
|
||||
return arr[1];
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an event and subscription ID from an EVENT envelope.
|
||||
* Expected Format: ["EVENT", subID, event]
|
||||
*/
|
||||
export function findSubscriptionEvent(
|
||||
env: Envelope,
|
||||
): [subID: string, event: any] {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 3);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "EVENT");
|
||||
|
||||
const subID = arr[1];
|
||||
if (typeof subID !== "string") {
|
||||
throw new WrongFieldTypeError("subscription ID is not a string");
|
||||
}
|
||||
|
||||
return [subID, arr[2]];
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts eventID, status, and message from an OK envelope.
|
||||
* Expected Format: ["OK", eventID, status, message]
|
||||
*/
|
||||
export function findOK(
|
||||
env: Envelope,
|
||||
): [eventID: string, status: boolean, message: string] {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 4);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "OK");
|
||||
|
||||
const eventID = arr[1];
|
||||
if (typeof eventID !== "string") {
|
||||
throw new WrongFieldTypeError("event ID is not a string");
|
||||
}
|
||||
|
||||
if (typeof arr[2] !== "boolean") {
|
||||
throw new WrongFieldTypeError("status is not the expected type");
|
||||
}
|
||||
const status = arr[2];
|
||||
|
||||
const message = arr[3];
|
||||
if (typeof message !== "string") {
|
||||
throw new WrongFieldTypeError("message is not a string");
|
||||
}
|
||||
|
||||
return [eventID, status, message];
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts subscription ID and filters from a REQ envelope.
|
||||
* Expected Format: ["REQ", subID, filter1, filter2, ...]
|
||||
*/
|
||||
export function findReq(env: Envelope): [subID: string, filters: any[]] {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 2);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "REQ");
|
||||
|
||||
const subID = arr[1];
|
||||
if (typeof subID !== "string") {
|
||||
throw new WrongFieldTypeError("subscription ID is not a string");
|
||||
}
|
||||
|
||||
const filters: any[] = [];
|
||||
for (let i = 2; i < arr.length; i++) {
|
||||
filters.push(arr[i]);
|
||||
}
|
||||
|
||||
return [subID, filters];
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts subscription ID from an EOSE envelope.
|
||||
* Expected Format: ["EOSE", subID]
|
||||
*/
|
||||
export function findEOSE(env: Envelope): string {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 2);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "EOSE");
|
||||
|
||||
const subID = arr[1];
|
||||
if (typeof subID !== "string") {
|
||||
throw new WrongFieldTypeError("subscription ID is not a string");
|
||||
}
|
||||
|
||||
return subID;
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts subscription ID from a CLOSE envelope.
|
||||
* Expected Format: ["CLOSE", subID]
|
||||
*/
|
||||
export function findClose(env: Envelope): string {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 2);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "CLOSE");
|
||||
|
||||
const subID = arr[1];
|
||||
if (typeof subID !== "string") {
|
||||
throw new WrongFieldTypeError("subscription ID is not a string");
|
||||
}
|
||||
|
||||
return subID;
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts subscription ID and message from a CLOSED envelope.
|
||||
* Expected Format: ["CLOSED", subID, message]
|
||||
*/
|
||||
export function findClosed(env: Envelope): [subID: string, message: string] {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 3);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "CLOSED");
|
||||
|
||||
const subID = arr[1];
|
||||
if (typeof subID !== "string") {
|
||||
throw new WrongFieldTypeError("subscription ID is not a string");
|
||||
}
|
||||
|
||||
const message = arr[2];
|
||||
if (typeof message !== "string") {
|
||||
throw new WrongFieldTypeError("message is not a string");
|
||||
}
|
||||
|
||||
return [subID, message];
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts message from a NOTICE envelope.
|
||||
* Expected Format: ["NOTICE", message]
|
||||
*/
|
||||
export function findNotice(env: Envelope): string {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 2);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "NOTICE");
|
||||
|
||||
const message = arr[1];
|
||||
if (typeof message !== "string") {
|
||||
throw new WrongFieldTypeError("message is not a string");
|
||||
}
|
||||
|
||||
return message;
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts challenge from an AUTH challenge envelope.
|
||||
* Expected Format: ["AUTH", challenge]
|
||||
*/
|
||||
export function findAuthChallenge(env: Envelope): string {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 2);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "AUTH");
|
||||
|
||||
const challenge = arr[1];
|
||||
if (typeof challenge !== "string") {
|
||||
throw new WrongFieldTypeError("challenge is not a string");
|
||||
}
|
||||
|
||||
return challenge;
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts event from an AUTH response envelope.
|
||||
* Expected Format: ["AUTH", event]
|
||||
*/
|
||||
export function findAuthResponse(env: Envelope): any {
|
||||
try {
|
||||
const arr = JSON.parse(env);
|
||||
checkArrayLength(arr, 2);
|
||||
|
||||
const label = arr[0];
|
||||
if (typeof label !== "string") {
|
||||
throw new WrongFieldTypeError("envelope label is not a string");
|
||||
}
|
||||
checkLabel(label, "AUTH");
|
||||
|
||||
// The second element should be an object (the event)
|
||||
return arr[1];
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw err;
|
||||
}
|
||||
throw new InvalidJSONError(`${err}`);
|
||||
}
|
||||
}
|
||||
3
src/envelope/index.ts
Normal file
3
src/envelope/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./envelope";
|
||||
export * from "./enclose";
|
||||
export * from "./find";
|
||||
70
src/errors/index.ts
Normal file
70
src/errors/index.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Base error class for all roots-ws errors.
|
||||
*/
|
||||
export class RootsWSError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = this.constructor.name;
|
||||
// This sets the prototype correctly for instanceof checks
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Structure Errors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that a byte sequence could not be parsed as valid JSON.
|
||||
* This is typically returned when unmarshaling fails during envelope processing.
|
||||
*/
|
||||
export class InvalidJSONError extends RootsWSError {
|
||||
constructor(message: string = "invalid JSON") {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a required field is absent from a data structure.
|
||||
* This is returned when validating that all mandatory components are present.
|
||||
*/
|
||||
export class MissingFieldError extends RootsWSError {
|
||||
constructor(message: string = "missing required field") {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a field's type does not match the expected type.
|
||||
* This is returned when unmarshaling a specific value fails due to type mismatch.
|
||||
*/
|
||||
export class WrongFieldTypeError extends RootsWSError {
|
||||
constructor(message: string = "wrong field type") {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envelope Errors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that a message does not conform to the Nostr envelope structure.
|
||||
* This typically occurs when an array has incorrect number of elements for its message type.
|
||||
*/
|
||||
export class InvalidEnvelopeError extends RootsWSError {
|
||||
constructor(message: string = "invalid envelope format") {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that an envelope's label does not match the expected type.
|
||||
* This is returned when attempting to parse an envelope using a Find function that
|
||||
* expects a different label than what was provided.
|
||||
*/
|
||||
export class WrongEnvelopeLabelError extends RootsWSError {
|
||||
constructor(message: string = "wrong envelope label") {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
1
src/index.ts
Normal file
1
src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./types";
|
||||
29
src/types.ts
Normal file
29
src/types.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Represents a Nostr websocket message.
|
||||
*/
|
||||
export type Envelope = string;
|
||||
|
||||
/**
|
||||
* Represents the current state of a WebSocket connection.
|
||||
*/
|
||||
export enum ConnectionStatus {
|
||||
/**
|
||||
* Indicates the connection is not active and no connection attempt is in progress.
|
||||
*/
|
||||
Disconnected = 0,
|
||||
|
||||
/**
|
||||
* Indicates a connection attempt is currently in progress but not yet established.
|
||||
*/
|
||||
Connecting = 1,
|
||||
|
||||
/**
|
||||
* Indicates the connection is active and ready for message exchange.
|
||||
*/
|
||||
Connected = 2,
|
||||
|
||||
/**
|
||||
* Indicates the connection is in the process of shutting down gracefully.
|
||||
*/
|
||||
Closing = 3,
|
||||
}
|
||||
Reference in New Issue
Block a user