checkpoint

pull/134/head
Alex Kern 1 year ago
parent 3c17d2604f
commit def5707932
No known key found for this signature in database
GPG Key ID: EF051FACCACBEE25

@ -24,8 +24,6 @@
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"debug": "^4.3.6", "debug": "^4.3.6",
"express": "^4.19.2", "express": "^4.19.2",
"fp-ts": "^2.16.9",
"io-ts": "^2.2.21",
"ioredis": "^4.28.5", "ioredis": "^4.28.5",
"next": "^14.2.8", "next": "^14.2.8",
"nodemon": "^1.19.4", "nodemon": "^1.19.4",
@ -42,7 +40,8 @@
"twilio": "^2.11.1", "twilio": "^2.11.1",
"use-http": "^1.0.28", "use-http": "^1.0.28",
"web-streams-polyfill": "^3.3.3", "web-streams-polyfill": "^3.3.3",
"webrtcsupport": "^2.2.0" "webrtcsupport": "^2.2.0",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",

@ -74,6 +74,9 @@ dependencies:
webrtcsupport: webrtcsupport:
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0 version: 2.2.0
zod:
specifier: ^3.23.8
version: 3.23.8
devDependencies: devDependencies:
'@types/debug': '@types/debug':
@ -9401,3 +9404,7 @@ packages:
/yocto-queue@0.1.0: /yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
/zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
dev: false

@ -10,7 +10,7 @@ import {
mobileVendor, mobileVendor,
mobileModel, mobileModel,
} from 'react-device-detect' } from 'react-device-detect'
import * as t from 'io-ts' import { z } from 'zod' // Add this import
import { ChunkMessage, decodeMessage, Message, MessageType } from '../messages' import { ChunkMessage, decodeMessage, Message, MessageType } from '../messages'
import { createZipStream } from '../zip-stream' import { createZipStream } from '../zip-stream'
import { DataConnection } from 'peerjs' import { DataConnection } from 'peerjs'
@ -110,7 +110,7 @@ export default function Downloader({
type: string type: string
}> | null>(null) }> | null>(null)
const processChunk = useRef< const processChunk = useRef<
((message: t.TypeOf<typeof ChunkMessage>) => void) | null ((message: z.infer<typeof ChunkMessage>) => void) | null
>(null) >(null)
const [shouldAttemptConnection, setShouldAttemptConnection] = useState(false) const [shouldAttemptConnection, setShouldAttemptConnection] = useState(false)
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@ -133,7 +133,7 @@ export default function Downloader({
const handleOpen = () => { const handleOpen = () => {
setOpen(true) setOpen(true)
const request: t.TypeOf<typeof Message> = { const request: z.infer<typeof Message> = {
type: MessageType.RequestInfo, type: MessageType.RequestInfo,
browserName: browserName, browserName: browserName,
browserVersion: browserVersion, browserVersion: browserVersion,
@ -149,7 +149,7 @@ export default function Downloader({
const handleData = (data: unknown) => { const handleData = (data: unknown) => {
try { try {
const message = decodeMessage(data) const message = Message.parse(data) // Use Zod's parse method
switch (message.type) { switch (message.type) {
case MessageType.Info: case MessageType.Info:
setFilesInfo(message.files) setFilesInfo(message.files)
@ -248,7 +248,7 @@ export default function Downloader({
return return
} }
const request: t.TypeOf<typeof Message> = { const request: z.infer<typeof Message> = {
type: MessageType.Start, type: MessageType.Start,
fileName: filesInfo[nextFileIndex].fileName, fileName: filesInfo[nextFileIndex].fileName,
offset: 0, offset: 0,
@ -257,7 +257,7 @@ export default function Downloader({
nextFileIndex++ nextFileIndex++
} }
const processChunkFunc = (message: t.TypeOf<typeof ChunkMessage>): void => { const processChunkFunc = (message: z.infer<typeof ChunkMessage>): void => {
const fileStream = fileStreamByPath[message.fileName] const fileStream = fileStreamByPath[message.fileName]
if (!fileStream) { if (!fileStream) {
console.error('no stream found for ' + message.fileName) console.error('no stream found for ' + message.fileName)
@ -291,7 +291,7 @@ export default function Downloader({
downloadPromise downloadPromise
.then(() => { .then(() => {
const request: t.TypeOf<typeof Message> = { const request: z.infer<typeof Message> = {
type: MessageType.Done, type: MessageType.Done,
} }
dataConnection.send(request) dataConnection.send(request)

@ -6,7 +6,6 @@ import {
UploaderConnectionStatus, UploaderConnectionStatus,
} from '../types' } from '../types'
import { decodeMessage, Message, MessageType } from '../messages' import { decodeMessage, Message, MessageType } from '../messages'
import * as t from 'io-ts'
import { getFileName } from '../fs' import { getFileName } from '../fs'
// TODO(@kern): Test for better values // TODO(@kern): Test for better values
@ -59,7 +58,7 @@ export function useUploaderConnections(
switch (message.type) { switch (message.type) {
case MessageType.RequestInfo: { case MessageType.RequestInfo: {
if (message.password !== password) { if (message.password !== password) {
const request: t.TypeOf<typeof Message> = { const request: Message = {
type: MessageType.Error, type: MessageType.Error,
error: 'Invalid password', error: 'Invalid password',
} }
@ -105,13 +104,13 @@ export function useUploaderConnections(
const fileInfo = files.map((f) => { const fileInfo = files.map((f) => {
return { return {
fileName: f.fileName ?? f.name ?? '', fileName: getFileName(f),
size: f.size, size: f.size,
type: f.type, type: f.type,
} }
}) })
const request: t.TypeOf<typeof Message> = { const request: Message = {
type: MessageType.Info, type: MessageType.Info,
files: fileInfo, files: fileInfo,
} }
@ -142,7 +141,7 @@ export function useUploaderConnections(
const end = Math.min(file.size, offset + MAX_CHUNK_SIZE) const end = Math.min(file.size, offset + MAX_CHUNK_SIZE)
const chunkSize = end - offset const chunkSize = end - offset
const final = chunkSize < MAX_CHUNK_SIZE const final = chunkSize < MAX_CHUNK_SIZE
const request: t.TypeOf<typeof Message> = { const request: Message = {
type: MessageType.Chunk, type: MessageType.Chunk,
fileName, fileName,
offset, offset,

@ -1,82 +1,70 @@
import * as t from 'io-ts' import { z } from 'zod'
import { pipe } from 'fp-ts/function'
import { fold } from 'fp-ts/Either'
export enum MessageType { export enum MessageType {
RequestInfo = 'REQUEST_INFO', RequestInfo = 'RequestInfo',
Info = 'INFO', Info = 'Info',
Pause = 'PAUSE', Start = 'Start',
Start = 'START', Chunk = 'Chunk',
Chunk = 'CHUNK', Done = 'Done',
Done = 'DONE', Error = 'Error',
Error = 'ERROR',
} }
export const RequestInfoMessage = t.type({ export const RequestInfoMessage = z.object({
type: t.literal(MessageType.RequestInfo), type: z.literal(MessageType.RequestInfo),
browserName: t.string, browserName: z.string(),
browserVersion: t.string, browserVersion: z.string(),
osName: t.string, osName: z.string(),
osVersion: t.string, osVersion: z.string(),
mobileVendor: t.string, mobileVendor: z.string(),
mobileModel: t.string, mobileModel: z.string(),
password: t.string, password: z.string(),
}) })
export const InfoMessage = t.type({ export const InfoMessage = z.object({
type: t.literal(MessageType.Info), type: z.literal(MessageType.Info),
files: t.array( files: z.array(
t.type({ z.object({
fileName: t.string, fileName: z.string(),
size: t.number, size: z.number(),
type: t.string, type: z.string(),
}), }),
), ),
}) })
export const StartMessage = t.type({ export const StartMessage = z.object({
type: t.literal(MessageType.Start), type: z.literal(MessageType.Start),
fileName: t.string, fileName: z.string(),
offset: t.number, offset: z.number(),
}) })
export const ChunkMessage = t.type({ export const ChunkMessage = z.object({
type: t.literal(MessageType.Chunk), type: z.literal(MessageType.Chunk),
fileName: t.string, fileName: z.string(),
offset: t.number, offset: z.number(),
bytes: t.unknown, bytes: z.unknown(),
final: t.boolean, final: z.boolean(),
}) })
export const PauseMessage = t.type({ export const DoneMessage = z.object({
type: t.literal(MessageType.Pause), type: z.literal(MessageType.Done),
}) })
export const DoneMessage = t.type({ export const ErrorMessage = z.object({
type: t.literal(MessageType.Done), type: z.literal(MessageType.Error),
error: z.string(),
}) })
export const ErrorMessage = t.type({ export const Message = z.discriminatedUnion('type', [
type: t.literal(MessageType.Error),
error: t.string,
})
export const Message = t.union([
RequestInfoMessage, RequestInfoMessage,
InfoMessage, InfoMessage,
PauseMessage,
StartMessage, StartMessage,
ChunkMessage, ChunkMessage,
DoneMessage, DoneMessage,
ErrorMessage, ErrorMessage,
]) ])
export function decodeMessage(data: any): t.TypeOf<typeof Message> { export type Message = z.infer<typeof Message>
const onFailure = (errors: t.Errors): t.TypeOf<typeof Message> => {
throw new Error(`${errors.length} error(s) found`)
}
const onSuccess = (mesg: t.TypeOf<typeof Message>) => mesg
return pipe(Message.decode(data), fold(onFailure, onSuccess)) export function decodeMessage(data: unknown): Message {
return Message.parse(data)
} }

Loading…
Cancel
Save