Compare commits

...

6 Commits

Author SHA1 Message Date
11827c30c1 fix misc
All checks were successful
Build and push image for doorman-homeassistant / docker (push) Successful in 49s
Build and push Doorman UI / API / docker (push) Successful in 1m40s
Build and push image for doorman-homeassistant / deploy-gitainer (push) Successful in 5s
2025-06-08 11:49:53 -07:00
9c6932d050 fix timeout coersion 2025-06-08 11:49:36 -07:00
8ca379b6db update onboard route 2025-06-07 16:55:29 -07:00
1c87e93d2f update status route 2025-06-07 16:37:01 -07:00
8750c2ed82 update notify route 2025-06-07 16:30:14 -07:00
e17bb267fe update info route 2025-06-07 16:15:33 -07:00
8 changed files with 175 additions and 199 deletions

View File

@ -20,7 +20,7 @@ export const AuthRequestSchema = z.object({
door: z.string(), door: z.string(),
key: z.string(), key: z.string(),
ip: z.string().optional(), ip: z.string().optional(),
timeout: z.number().gt(0, "Timeout cannot be 0").optional(), timeout: z.coerce.number().gt(0, "Timeout cannot be 0").optional(),
}); });
export type AuthRequest = z.infer<typeof AuthRequestSchema>; export type AuthRequest = z.infer<typeof AuthRequestSchema>;

View File

@ -6,38 +6,40 @@ import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs
import { TwilioContext } from "../../../types/TwilioContext"; import { TwilioContext } from "../../../types/TwilioContext";
import { createDynaBridgeClient } from "../../../utils/ddb"; import { createDynaBridgeClient } from "../../../utils/ddb";
import { DoorStatus } from "../../../types/DoorStatus"; import { DoorStatus } from "../../../types/DoorStatus";
import { getDoorConfigID } from "../../../schema/DoorConfig"; import { DoorConfigSchema, getDoorConfigID } from "../../../schema/DoorConfig";
import { getDoorAliasID } from "../../../schema/DoorAlias"; import { getDoorAliasID } from "../../../schema/DoorAlias";
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus"; import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
import { withMetrics } from "../../../common/DoormanHandler";
import { z } from "zod";
import { UserAgentHeader } from "../../../utils/blockUserAgent";
import { setResponseJson } from "../../../utils/responseUtils";
export interface InfoRequest extends ServerlessEventObject { export const InfoRequestSchema = z.object({
door?: string; door: z.string().optional(),
buzzer?: string; buzzer: z.string().optional(),
})
.partial()
.refine(data => data.buzzer || data.door, 'Buzzer or door must be provided');
// TODO: change these to be multiple export type InfoRequest = z.infer<typeof InfoRequestSchema>;
discordUser: string; export interface InfoRequestTwilio extends ServerlessEventObject<InfoRequest, UserAgentHeader> { };
msg: string;
json: string;
}
export interface InfoResponseClient { export const InfoResponseClientSchema = DoorConfigSchema
buzzer: string; .omit({ PK: true, SK: true, pin: true })
door: string; .extend({ door: z.string() });
pressKey: string;
fallbackNumbers: string[];
discordUsers: string[];
}
export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> = async function(context, event, callback) { export type InfoResponseClient = z.infer<typeof InfoResponseClientSchema>;
export const InfoResponseUISchema = InfoResponseClientSchema.extend({ id: z.string(), status: z.nativeEnum(DoorStatus) });
export type InfoResponseUI = z.infer<typeof InfoResponseUISchema>;
export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequestTwilio> = withMetrics("info", async (context, event, callback, metricsRegistry) => {
const response = new Twilio.Response(); const response = new Twilio.Response();
const req = InfoRequestSchema.parse(event);
let door = event.door; let door = req.door;
const buzzer = event.buzzer?.slice(-10); const buzzer = req.buzzer?.slice(-10);
if (!door && !buzzer) {
response.setStatusCode(400);
return callback(null, response);
}
const db = createDynaBridgeClient(context); const db = createDynaBridgeClient(context);
@ -45,10 +47,9 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
door = await db.entities.doorAlias.findById(getDoorAliasID(buzzer)) door = await db.entities.doorAlias.findById(getDoorAliasID(buzzer))
.then(async (alias) => { .then(async (alias) => {
if (!alias) { if (!alias) {
response setResponseJson(response, 404, {
.setStatusCode(404) err: "This buzzer is not registered",
.appendHeader('Content-Type', 'application/json') });
.setBody({ err: "This buzzer is not registered" });
return undefined; return undefined;
} }
return alias.name; return alias.name;
@ -59,42 +60,32 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
const config = await db.entities.doorConfig.findById(getDoorConfigID(door)); const config = await db.entities.doorConfig.findById(getDoorConfigID(door));
if (!config) { if (!config) {
response setResponseJson(response, 404, {
.setStatusCode(404) err: "This buzzer is not registered",
.appendHeader('Content-Type', 'application/json') });
.setBody({ err: "This buzzer is not registered properly" });
} else { } else {
if (buzzer) { if (buzzer) {
// respond to twilio CLIENT // respond to twilio CLIENT
response const body: InfoResponseClient = InfoResponseClientSchema.parse({
.setStatusCode(200) ...config,
.appendHeader('Content-Type', 'application/json') buzzer,
.setBody({ door,
buzzer, });
door,
fallbackNumbers: config.fallbackNumbers, setResponseJson(response, 200, body);
pressKey: config.pressKey,
discordUsers: config.discordUsers,
});
} else { } else {
const lock = await db.entities.lockStatus.findById(getLockStatusID(door)); const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED; const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
const body: InfoResponseUI = InfoResponseUISchema.parse({
...config,
id: door,
door,
status,
})
// respond to UI // respond to UI
response setResponseJson(response, 200, body);
.setStatusCode(200)
.appendHeader('Content-Type', 'application/json')
.setBody({
id: door,
timeout: config.timeout,
buzzer: config.buzzer,
status,
buzzerCode: config.buzzerCode,
fallbackNumbers: config.fallbackNumbers,
pressKey: config.pressKey,
discordUsers: config.discordUsers,
greeting: config.greeting || "",
});
} }
} }
} }
@ -104,4 +95,4 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
db.ddbClient.destroy(); db.ddbClient.destroy();
return callback(null, response); return callback(null, response);
}; });

View File

@ -4,49 +4,46 @@ import { jsonMsgSuffix, sendMessageToUser } from "../../../utils/discord";
import { getMetricFromRegistry, withMetrics } from "../../../common/DoormanHandler"; import { getMetricFromRegistry, withMetrics } from "../../../common/DoormanHandler";
import { NotifyMetrics, registerMetrics } from "../../../metrics/NotifyMetrics"; import { NotifyMetrics, registerMetrics } from "../../../metrics/NotifyMetrics";
import { Counter, Summary } from "prom-client"; import { Counter, Summary } from "prom-client";
import { z } from "zod";
import zu from "zod_utilz";
import { setResponseJson } from "../../../utils/responseUtils";
import { UserAgentHeader } from "../../../utils/blockUserAgent";
export const NotifyRequestSchema = z.object({
key: z.string(),
export interface NotifyRequest extends ServerlessEventObject { // these must be arrays
door: string; discordUser: zu.stringToJSON().refine(arg => arg instanceof Array),
msg: zu.stringToJSON().refine(arg => arg instanceof Array),
json: zu.stringToJSON().refine(arg => arg instanceof Array)
});
key: string; export type NotifyRequest = z.infer<typeof NotifyRequestSchema>;
// these are arrays in the request export interface NotifyRequestTwilio extends ServerlessEventObject<NotifyRequest, UserAgentHeader> { }
discordUser: string;
msg: string;
json: string;
}
export const handler: ServerlessFunctionSignature<TwilioContext, NotifyRequest> = withMetrics('notify', async (context, event, callback, metricsRegistry) => { export const handler: ServerlessFunctionSignature<TwilioContext, NotifyRequestTwilio> = withMetrics('notify', async (context, event, callback, metricsRegistry) => {
const response = new Twilio.Response(); const response = new Twilio.Response();
registerMetrics(metricsRegistry); registerMetrics(metricsRegistry);
const req = NotifyRequestSchema.parse(event);
// secure notify endpoint // secure notify endpoint
if (event.key !== context.NOTIFY_SECRET_KEY) { if (req.key !== context.NOTIFY_SECRET_KEY) {
getMetricFromRegistry<Counter>(metricsRegistry, NotifyMetrics.UNAUTHENTICATED_CALL).inc(1); getMetricFromRegistry<Counter>(metricsRegistry, NotifyMetrics.UNAUTHENTICATED_CALL).inc(1);
response setResponseJson(response, 401, {
.setStatusCode(401) err: "Unauthenticated call", event
.appendHeader('Content-Type', 'application/json') });
.setBody({ err: "Unauthenticated call", event });
return callback(null, response); return callback(null, response);
} }
let users: string[]; let users: string[] = req.discordUser as string[];
let msgs: string[]; let msgs: string[] = req.msg as string[];
let jsons: string[]; let jsons: string[] = req.json as string[];
let promises = []; let promises = [];
try { try {
users = JSON.parse(event.discordUser);
console.log(users);
msgs = JSON.parse(event.msg);
console.log("before parsing", event.json);
jsons = JSON.parse(event.json);
console.log("after parsing", event.json);
const recordNotifyLatency = getMetricFromRegistry<Summary>(metricsRegistry, NotifyMetrics.DISCORD_LATENCY) const recordNotifyLatency = getMetricFromRegistry<Summary>(metricsRegistry, NotifyMetrics.DISCORD_LATENCY)
.startTimer(); .startTimer();
@ -66,10 +63,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, NotifyRequest>
); );
} catch (e) { } catch (e) {
console.error(e); console.error(e);
response setResponseJson(response, 500, { err: e, event });
.setStatusCode(500)
.appendHeader('Content-Type', 'application/json')
.setBody({ err: e, event });
return callback(null, response); return callback(null, response);
} }

View File

@ -8,29 +8,37 @@ import { createDynaBridgeClient } from "../../../utils/ddb";
import DiscordOauth2 from "discord-oauth2"; import DiscordOauth2 from "discord-oauth2";
import { DoorAliasSchema, getDoorAliasID } from "../../../schema/DoorAlias"; import { DoorAliasSchema, getDoorAliasID } from "../../../schema/DoorAlias";
import { Counter } from "prom-client"; import { Counter } from "prom-client";
import { z } from "zod";
import zu from "zod_utilz";
import { setResponseJson } from "../../../utils/responseUtils";
export interface OnboardRequest extends ServerlessEventObject<{}, UserAgentHeader> { export const OnboardRequestSchema = z.object({
newConfig?: string; newConfig: zu.stringToJSON().optional(),
code: z.string().optional(),
state: zu.stringToJSON().optional(),
}).partial()
.refine(data => data.newConfig || (data.code && data.state), 'newConfig or (code and state) must be specified');
// for oauth redirect export type OnboardRequest = z.infer<typeof OnboardRequestSchema>;
code?: string;
state?: string;
}
export interface DiscordOnboardingState { export interface OnboardRequestTwilio extends ServerlessEventObject<OnboardRequest, UserAgentHeader> { };
name: string;
id: string; export const DiscordOnboardingStateSchema = z.object({
apiRedirect: string; name: z.string(),
} id: z.string(),
apiRedirect: z.string(),
});
export type DiscordOnboardingState = z.infer<typeof DiscordOnboardingStateSchema>;
const ONBOARDING_SCOPE = ['identify', 'email', 'guilds.join']; const ONBOARDING_SCOPE = ['identify', 'email', 'guilds.join'];
export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest> = withMetrics('onboard', async (context, event, callback, metricsRegistry) => { export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestTwilio> = withMetrics('onboard', async (context, event, callback, metricsRegistry) => {
const response = new Twilio.Response(); const response = new Twilio.Response();
response.appendHeader('Content-Type', 'application/json');
registerMetrics(metricsRegistry); registerMetrics(metricsRegistry);
const req = OnboardRequestSchema.parse(event);
const db = createDynaBridgeClient(context); const db = createDynaBridgeClient(context);
// return oauth link // return oauth link
@ -41,23 +49,13 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
}); });
// create door config route // create door config route
if (event.code && event.state) { if (req.code && req.state) {
const code = event.code; const code = req.code;
let discordState: DiscordOnboardingState; let discordState: DiscordOnboardingState = DiscordOnboardingStateSchema.parse(req.state);
try {
discordState = JSON.parse(event.state);
} catch (e) {
response.setStatusCode(400);
response.setBody({ err: "invalid state" });
return callback(null, response);
}
const config = await db.entities.onboardDoorConfig.findById(getOnboardDoorId(discordState.name)); const config = await db.entities.onboardDoorConfig.findById(getOnboardDoorId(discordState.name));
if (!config || config.nonce !== discordState.id) { if (!config || config.nonce !== discordState.id) {
response.setStatusCode(404) setResponseJson(response, 404, { err: "approval not found" });
.setBody({ err: "approval not found" });
return callback(null, response); return callback(null, response);
} }
@ -73,8 +71,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
}); });
} catch (err) { } catch (err) {
console.log("something went wrong with discord authorization"); console.log("something went wrong with discord authorization");
response.setStatusCode(401); setResponseJson(response, 401, { err });
response.setBody({ err });
return callback(null, response); return callback(null, response);
} }
@ -84,9 +81,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
console.log(profileId); console.log(profileId);
if (!profileId) { if (!profileId) {
response.setStatusCode(404); setResponseJson(response, 404, { err: "profile not found" });
response.setBody({ err: "profile not found" });
return callback(null, response); return callback(null, response);
} }
@ -144,37 +139,26 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
// and all must succeed or none succeed // and all must succeed or none succeed
try { try {
await db.transaction([createDoorAlias, createDoorConfig, deleteOnboardingConfig]); await db.transaction([createDoorAlias, createDoorConfig, deleteOnboardingConfig]);
response.setStatusCode(200); setResponseJson(response, 200, { redirect: context.DOORMAN_URL + `?door=${config.name}` });
response.setBody({ redirect: context.DOORMAN_URL + `?door=${config.name}` });
} catch (e) { } catch (e) {
getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.TRANSACTION_CONFLICT).inc(1); getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.TRANSACTION_CONFLICT).inc(1);
console.error(e); console.error(e);
response.setStatusCode(409); setResponseJson(response, 409, { err: "something went wrong during onboarding" });
response.setBody({ err: "something went wrong during onboarding" });
} }
return callback(null, response); return callback(null, response);
} }
let newConfig = event.newConfig; let newConfig = req.newConfig;
if (!newConfig) { if (!newConfig) {
response.setStatusCode(400); setResponseJson(response, 400, { err: "missing newConfig" });
return callback(null, response); return callback(null, response);
} }
let newConfigParsed: OnboardDoorReq;
console.log("parsing config"); console.log("parsing config");
try { let newConfigParsed: OnboardDoorReq = OnboardDoorReqSchema.parse(newConfig);
newConfigParsed = OnboardDoorReqSchema.parse(JSON.parse(newConfig));
} catch (err) {
response.setStatusCode(400);
response.setBody({ err });
return callback(null, response);
}
const newConfigObj = createOnboardDoorConfig(newConfigParsed); const newConfigObj = createOnboardDoorConfig(newConfigParsed);
// check if this name or buzzer is already registered? // check if this name or buzzer is already registered?
@ -182,8 +166,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
if (existingAlias) { if (existingAlias) {
getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.BUZZER_CONFLICT).inc({ buzzer: newConfigObj.buzzer }, 1); getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.BUZZER_CONFLICT).inc({ buzzer: newConfigObj.buzzer }, 1);
response.setStatusCode(409); setResponseJson(response, 409, { err: `A buzzer is already registered with the number ${newConfigObj.buzzer}` });
response.setBody({ err: `A buzzer is already registered with the number ${newConfigObj.buzzer}` });
return callback(null, response); return callback(null, response);
} }
@ -191,8 +174,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
if (existingConfig) { if (existingConfig) {
getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.NAME_CONFLICT).inc({ name: newConfigObj.name }, 1); getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.NAME_CONFLICT).inc({ name: newConfigObj.name }, 1);
response.setStatusCode(409); setResponseJson(response, 409, { err: `A buzzer is already registered with the name ${newConfigObj.name}` });
response.setBody({ err: `A buzzer is already registered with the name ${newConfigObj.name}` });
return callback(null, response); return callback(null, response);
} }
@ -202,11 +184,11 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
// save to DB, this is a middle step until discord auth comes in // save to DB, this is a middle step until discord auth comes in
await db.entities.onboardDoorConfig.save(newConfigObj); await db.entities.onboardDoorConfig.save(newConfigObj);
const discordState: DiscordOnboardingState = { const discordState: DiscordOnboardingState = DiscordOnboardingStateSchema.parse({
name: newConfigObj.name, name: newConfigObj.name,
id: newConfigObj.nonce, id: newConfigObj.nonce,
apiRedirect: '/api/door/onboard', apiRedirect: '/api/door/onboard',
} });
const redirect = oauth.generateAuthUrl({ const redirect = oauth.generateAuthUrl({
scope: ONBOARDING_SCOPE, scope: ONBOARDING_SCOPE,
@ -214,7 +196,6 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
state: JSON.stringify(discordState), state: JSON.stringify(discordState),
}); });
response.setStatusCode(200); setResponseJson(response, 200, { redirect });
response.setBody({ redirect });
return callback(null, response); return callback(null, response);
}); });

View File

@ -4,34 +4,35 @@
import { ServerlessFunctionSignature, ServerlessEventObject } from "@twilio-labs/serverless-runtime-types/types"; import { ServerlessFunctionSignature, ServerlessEventObject } from "@twilio-labs/serverless-runtime-types/types";
import { TwilioContext } from "../../../types/TwilioContext"; import { TwilioContext } from "../../../types/TwilioContext";
import { shouldBlockRequest } from "../../../utils/blockUserAgent"; import { UserAgentHeader } from "../../../utils/blockUserAgent";
import { createDynaBridgeClient } from "../../../utils/ddb"; import { createDynaBridgeClient } from "../../../utils/ddb";
import { DoorStatus } from "../../../types/DoorStatus"; import { DoorStatus } from "../../../types/DoorStatus";
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus"; import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
import { withMetrics } from "../../../common/DoormanHandler";
import { z } from "zod";
import { setResponseJson } from "../../../utils/responseUtils";
import zu from "zod_utilz";
export interface StatusRequest extends ServerlessEventObject { export const StatusRequestSchema = z.object({
door: string; door: z.string(),
} });
export interface StatusResponse { export type StatusRequest = z.infer<typeof StatusRequestSchema>;
status: DoorStatus,
fingerprint: any;
}
export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest> = async function(context, event, callback) { export interface StatusRequestTwilio extends ServerlessEventObject<StatusRequest, UserAgentHeader> { }
export const StatusResponseSchema = z.object({
status: z.nativeEnum(DoorStatus),
fingerprint: zu.json().optional(),
})
export type StatusResponse = z.infer<typeof StatusResponseSchema>;
export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequestTwilio> = withMetrics('status', async (context, event, callback) => {
const response = new Twilio.Response(); const response = new Twilio.Response();
if (shouldBlockRequest(event)) { const req = StatusRequestSchema.parse(event);
response.setStatusCode(200); const door = req.door;
return callback(null, response);
}
const door = event.door;
if (!door) {
response.setStatusCode(400);
return callback(null, response);
}
const db = createDynaBridgeClient(context); const db = createDynaBridgeClient(context);
const lock = await db.entities.lockStatus.findById(getLockStatusID(door)); const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
@ -41,26 +42,24 @@ export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest>
if (isOpen && lock) { if (isOpen && lock) {
const fingerprint = JSON.parse(lock.fingerprint); const fingerprint = JSON.parse(lock.fingerprint);
response const body = StatusResponseSchema.parse({
.setStatusCode(200) status: DoorStatus.OPEN,
.appendHeader('Content-Type', 'application/json') fingerprint,
.setBody({ });
status: DoorStatus.OPEN,
fingerprint, setResponseJson(response, 200, body);
});
await db.entities.lockStatus.deleteById(getLockStatusID(door)); await db.entities.lockStatus.deleteById(getLockStatusID(door));
} else { } else {
response const body = StatusResponseSchema.parse({
.setStatusCode(200) status: DoorStatus.CLOSED,
.appendHeader('Content-Type', 'application/json')
.setBody({
status: DoorStatus.CLOSED,
}); });
setResponseJson(response, 200, body);
} }
// destroy the internal client after // destroy the internal client after
// @ts-ignore // @ts-ignore
db.ddbClient.destroy(); db.ddbClient.destroy();
return callback(null, response); return callback(null, response);
}; });

View File

@ -9,7 +9,7 @@ export async function getConfig(context: TwilioContext, buzzer: string): Promise
.then(res => res.json()) .then(res => res.json())
.catch(err => { .catch(err => {
return undefined; return undefined;
}); }) as InfoResponseClient;
} }
export async function notifyDiscord(context: TwilioContext, msg: string[], u: string[], optionalJsonStr: string[], metricsRegistry: Registry){ export async function notifyDiscord(context: TwilioContext, msg: string[], u: string[], optionalJsonStr: string[], metricsRegistry: Registry){

View File

@ -5,6 +5,7 @@ import { AuthComponent, IAuthMode } from "../components/AuthComponent";
import OtpInput from 'react-otp-input'; import OtpInput from 'react-otp-input';
import { CountdownBar } from "../components/CountdownBar"; import { CountdownBar } from "../components/CountdownBar";
import { DoorResponse } from "../types/DoorResponse"; import { DoorResponse } from "../types/DoorResponse";
import { fetchUrlEncoded } from "../helpers/FetchHelper";
export async function loader({ params, request }: any) { export async function loader({ params, request }: any) {
const door = new URL(request.url).searchParams.get('door'); const door = new URL(request.url).searchParams.get('door');
@ -13,7 +14,9 @@ export async function loader({ params, request }: any) {
return {}; return {};
} }
const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json()); const response = await fetchUrlEncoded('/api/door/info', {
door,
}).then(res => res.json());
console.log(response); console.log(response);
@ -70,7 +73,9 @@ export function DoorPage() {
} }
const timer = setInterval(async () => { const timer = setInterval(async () => {
const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json()); const response = await fetchUrlEncoded('/api/door/info', {
door,
}).then(res => res.json());
// polling assumes that the door was opened and whatever closed it was the buzzer system... // polling assumes that the door was opened and whatever closed it was the buzzer system...
// ie. state transition from OPEN to CLOSED before timeout means that twilio opened the door // ie. state transition from OPEN to CLOSED before timeout means that twilio opened the door

View File

@ -6,6 +6,7 @@ import { InputTokenGroup } from "../components/InputTokenGroup";
import { ReactNode, useState } from "react"; import { ReactNode, useState } from "react";
import CInput from "react-hook-form-cloudscape/components/input"; import CInput from "react-hook-form-cloudscape/components/input";
import CTextArea from "react-hook-form-cloudscape/components/textarea"; import CTextArea from "react-hook-form-cloudscape/components/textarea";
import { fetchUrlEncoded } from "../helpers/FetchHelper";
export type DoorEditForm = DoorResponse & { pin: string, fallbackNumber: string, discordUser: string, isConfirmed: boolean }; export type DoorEditForm = DoorResponse & { pin: string, fallbackNumber: string, discordUser: string, isConfirmed: boolean };
@ -107,25 +108,30 @@ export const EditPage = ({ isOnboarding }: EditPageProps) => {
timeout: parseInt("" + getValues("timeout")) timeout: parseInt("" + getValues("timeout"))
}; };
fetch(apiRoute + `?door=${door}&newConfig=${encodeURIComponent(JSON.stringify(form))}`) const body = {
.then(res => res.json()) door,
.then(res => { newConfig: JSON.stringify(form),
if (res.err) { };
addAlert("error", res.err);
return;
}
if (!isOnboarding) {
addAlert("success", `Created approval, check Discord notifcation from Doorman to confirm and approve the changes`);
} else if (res.redirect) {
// redirect for discord login
addAlert("in-progress", `Created Door, you will now be redirected to Discord login to complete the onboarding`);
// redirect in 2 seconds fetchUrlEncoded(apiRoute, body)
setTimeout(() => { .then(res => res.json())
window.location = res.redirect; .then(res => {
}, 2_000); if (res.err) {
} addAlert("error", res.err);
}) return;
}
if (!isOnboarding) {
addAlert("success", `Created approval, check Discord notifcation from Doorman to confirm and approve the changes`);
} else if (res.redirect) {
// redirect for discord login
addAlert("in-progress", `Created Door, you will now be redirected to Discord login to complete the onboarding`);
// redirect in 2 seconds
setTimeout(() => {
window.location = res.redirect;
}, 2_000);
}
});
}} }}
> >
Submit Submit