Skip to content

Commit

Permalink
feat: add better error typings (#103)
Browse files Browse the repository at this point in the history
* feat: add better error typings

* fix: update package.lock

* feat: refactor

* feat: update version

* fix: add try/catch and resolve

* fix: error schema

* fix: remove useless try/catch

* fix: add endline after package lock

* fix: add endline after package lock

* feat: rename error methods

* feat: update version of package
  • Loading branch information
pulkitb2 authored Jun 18, 2024
1 parent f20e36e commit 29c10cb
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 46 deletions.
4 changes: 2 additions & 2 deletions libs/iota-browser/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion libs/iota-browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@affinidi-tdk/iota-browser",
"version": "0.2.8",
"version": "0.2.9",
"description": "Browser module to fetch data through Affinidi Iota Framework",
"author": "Affinidi",
"repository": {
Expand Down
63 changes: 41 additions & 22 deletions libs/iota-browser/src/helpers/channel-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@ import { iot, mqtt5 } from 'aws-iot-device-sdk-v2/dist/browser'
import * as jose from 'jose'
import { v4 as uuidv4 } from 'uuid'
import {
EventTypes,
PrepareRequestEvent,
SignedRequestEvent,
SignedRequestEventSchema,
SignedRequestJWT,
SignedRequestJWTSchema,
} from '../validators/events'
import {
getUnexpectedErrorMessage,
ErrorCode,
throwEventParsingError,
} from '../validators/error'

const DEFAULT_IOT_ENDPOINT =
'a3sq1vuw0cw9an-ats.iot.ap-southeast-1.amazonaws.com'
Expand Down Expand Up @@ -147,6 +156,29 @@ export class ChannelProvider {
await client.subscribe(packet)
}

private getRequest(event: SignedRequestEvent) {
let signedRequest: SignedRequestEvent, signedRequestJWT: SignedRequestJWT
try {
signedRequest = SignedRequestEventSchema.parse(event)
} catch (e) {
throw Error(getUnexpectedErrorMessage(ErrorCode.SIGNED_REQUEST_EVENT))
}
try {
const claims = jose.decodeJwt(signedRequest.data.jwt)
signedRequestJWT = SignedRequestJWTSchema.parse(claims)
} catch (e) {
throw Error(getUnexpectedErrorMessage(ErrorCode.SIGNED_REQUEST_JWT))
}
const request: IotaChannelRequest = {
correlationId: signedRequest.correlationId,
payload: {
request: signedRequest.data.jwt,
client_id: signedRequestJWT.client_id,
},
}
return request
}

async prepareRequest(
params: PrepareRequestParams,
): Promise<IotaChannelRequest> {
Expand Down Expand Up @@ -178,30 +210,17 @@ export class ChannelProvider {
)
try {
const event = JSON.parse(raw_data)
if (
event.eventType === 'signedRequest' &&
correlationId === event.correlationId
) {
// TODO handle Zod errors gracefully
const signedRequest = SignedRequestEventSchema.parse(event)
const claims = jose.decodeJwt(signedRequest.data.jwt)
// TODO Zod validate JWT
if (!claims.client_id && typeof claims.client_id === 'string') {
reject(new Error('Unexpected request claims received'))
}
const client_id = claims.client_id as string
const request: IotaChannelRequest = {
correlationId: signedRequest.correlationId,
payload: {
request: signedRequest.data.jwt,
client_id,
},
}

if (correlationId !== event.correlationId) {
return
}
if (event.eventType === EventTypes.SignedRequest) {
const request = this.getRequest(event)
resolve(request)
} else if (event.eventType === EventTypes.Error) {
throwEventParsingError(event)
}
} catch (error) {
reject(error)
} catch (e) {
reject(e)
}
}
},
Expand Down
57 changes: 39 additions & 18 deletions libs/iota-browser/src/helpers/response-handler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { toUtf8 } from '@aws-sdk/util-utf8-browser'
import { mqtt5 } from 'aws-iot-device-sdk-v2/dist/browser'
import {
EventTypes,
ResponseCallbackEventSchema,
VerifiablePresentation,
VerifiablePresentationSchema,
ResponseCallbackEvent,
} from '../validators/events'
import { ChannelProvider } from './channel-provider'
import {
ErrorCode,
throwEventParsingError,
getUnexpectedErrorMessage,
} from '../validators/error'

export type IotaResponse = {
correlationId: string
Expand All @@ -26,6 +33,31 @@ export class ResponseHandler {
this.channelProvider = channelProvider
}

private getResponseHandler(event: ResponseCallbackEvent) {
let responseCallback: ResponseCallbackEvent, vpToken: VerifiablePresentation
try {
responseCallback = ResponseCallbackEventSchema.parse(event)
} catch (e) {
throw Error(getUnexpectedErrorMessage(ErrorCode.RESPONSE_CALLBACK_EVENT))
}
try {
vpToken = VerifiablePresentationSchema.parse(
JSON.parse(responseCallback.vpToken),
)
} catch (e) {
throw Error(
getUnexpectedErrorMessage(ErrorCode.VERIFIABLE_PRESENTATION_SCHEMA),
)
}
const response: IotaResponse = {
correlationId: responseCallback.correlationId,
vpToken,
// TODO parse presentation submission, same as vpToken
presentationSubmission: responseCallback.presentationSubmission,
}
return response
}

async getResponse(correlationId: string): Promise<IotaResponse> {
const client = this.channelProvider.getClient()
return new Promise((resolve, reject) => {
Expand All @@ -36,27 +68,16 @@ export class ResponseHandler {
const raw_data = toUtf8(
messageReceivedEvent.message.payload as Buffer,
)

try {
const event = JSON.parse(raw_data)
if (
event.eventType === 'response-callback' &&
event.correlationId === correlationId
) {
// TODO handle Zod errors gracefully
const responseCallback =
ResponseCallbackEventSchema.parse(event)
const vpToken = VerifiablePresentationSchema.parse(
JSON.parse(responseCallback.vpToken),
)
const response: IotaResponse = {
correlationId: responseCallback.correlationId,
vpToken,
// TODO parse presentation submission, same as vpToken
presentationSubmission:
responseCallback.presentationSubmission,
}
if (correlationId !== event.correlationId) {
return
}
if (event.eventType === EventTypes.ResponseCallback) {
const response = this.getResponseHandler(event)
resolve(response)
} else if (event.eventType === EventTypes.Error) {
throwEventParsingError(event)
}
} catch (error) {
reject(error)
Expand Down
34 changes: 34 additions & 0 deletions libs/iota-browser/src/validators/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ErrorEvent, ErrorEventSchema } from '../validators/events'

export enum ErrorCode {
'SIGNED_REQUEST_EVENT' = 'SignedRequestEvent',
'SIGNED_REQUEST_JWT' = 'SignedRequestJWT',
'RESPONSE_CALLBACK_EVENT' = 'ResponseCallbackEvent',
'VERIFIABLE_PRESENTATION_SCHEMA' = 'VerifiablePresentationSchema',
'ERROR_EVENT' = 'ErrorEvent',
}

function getIssue(errorEvent: ErrorEvent) {
return errorEvent.error.details![0].issues &&
errorEvent.error.details![0].issues.length > 0
? errorEvent.error.details![0].issues
: errorEvent.error.message
}

export function getUnexpectedErrorMessage(code: ErrorCode) {
return `Unexpected error occured. Error Code: ${code} `
}

export function formatEventError(errorEvent: ErrorEvent): string {
return `Something went wrong. ${getIssue(errorEvent)}. Error Code ${errorEvent.error.httpStatusCode}`
}

export function throwEventParsingError(event: ErrorEvent): never {
let errorEvent: ErrorEvent
try {
errorEvent = ErrorEventSchema.parse(event)
} catch (e) {
throw Error(getUnexpectedErrorMessage(ErrorCode.ERROR_EVENT))
}
throw Error(formatEventError(errorEvent))
}
11 changes: 8 additions & 3 deletions libs/iota-browser/src/validators/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod'

const EventTypes = {
export const EventTypes = {
PrepareRequest: 'prepareRequest',
SignedRequest: 'signedRequest',
ResponseCallback: 'response-callback',
Expand Down Expand Up @@ -28,6 +28,11 @@ export const SignedRequestEventSchema = BaseEvent.extend({
})
export type SignedRequestEvent = z.infer<typeof SignedRequestEventSchema>

export const SignedRequestJWTSchema = z.object({
client_id: z.string(),
})
export type SignedRequestJWT = z.infer<typeof SignedRequestJWTSchema>

// TODO full VP schema
export const VerifiablePresentationSchema = z.object({
verifiableCredential: z.array(z.object({ credentialSubject: z.any() })),
Expand All @@ -45,12 +50,12 @@ export type ResponseCallbackEvent = z.infer<typeof ResponseCallbackEventSchema>

const ERROR_LOCATION = ['body', 'path', 'query'] as const
const ErrorDetailItem = z.object({
issue: z.string(),
issues: z.string(),
field: z.string().optional(),
value: z.string().optional(),
location: z.enum(ERROR_LOCATION).optional(),
})
const ErrorEventSchema = BaseEvent.extend({
export const ErrorEventSchema = BaseEvent.extend({
eventType: z.literal(EventTypes.Error),
error: z.object({
message: z.string(),
Expand Down

0 comments on commit 29c10cb

Please sign in to comment.