mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-08-27 11:36:01 +08:00
Improves SMS error handling for common scenarios (#265)
This commit is contained in:
parent
c6b57abbde
commit
9ec841b149
8 changed files with 53 additions and 12 deletions
|
@ -66,7 +66,7 @@ export default class NexmoTextProvider extends TextProvider {
|
|||
|
||||
// Nexmo always returns 200 even for error
|
||||
if (responseMessage.status !== '0') {
|
||||
throw new TextError('nexmo', `Request failed with status: ${responseMessage.status}, error: ${responseMessage['error-text']}`)
|
||||
throw new TextError(this.type, this.phone_number, `Request failed with status: ${responseMessage.status}, error: ${responseMessage['error-text']}`)
|
||||
} else {
|
||||
return {
|
||||
message,
|
||||
|
@ -75,7 +75,7 @@ export default class NexmoTextProvider extends TextProvider {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
throw new TextError('nexmo', `Request failed with status ${response.status}`)
|
||||
throw new TextError(this.type, this.phone_number, `Request failed with status ${response.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ExternalProviderParams, ProviderControllers, ProviderSchema } from '../Provider'
|
||||
import { createController } from '../ProviderService'
|
||||
import TextError from './TextError'
|
||||
import { InboundTextMessage, TextMessage, TextResponse } from './TextMessage'
|
||||
import { TextProvider } from './TextProvider'
|
||||
|
||||
|
@ -66,7 +67,10 @@ export default class PlivoTextProvider extends TextProvider {
|
|||
response: responseBody.message_uuid[0],
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.status === 401 ? await response.text() : (await response.json()).error)
|
||||
const error = response.status === 401
|
||||
? await response.text()
|
||||
: (await response.json()).error
|
||||
throw new TextError(this.type, this.phone_number, error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ import { Variables } from '../../render'
|
|||
import { TextProvider } from './TextProvider'
|
||||
import { InboundTextMessage } from './TextMessage'
|
||||
import { UserEvent } from '../../users/UserEvent'
|
||||
import { UnsubscribeTextError } from './TextError'
|
||||
import { unsubscribe } from '../../subscriptions/SubscriptionService'
|
||||
|
||||
const TEXT_SEGMENT_LENGTH = 160
|
||||
|
||||
|
@ -19,10 +21,20 @@ export default class TextChannel {
|
|||
async send(template: TextTemplate, variables: Variables) {
|
||||
if (!variables.user.phone) throw new Error('Unable to send a text message to a user with no phone number.')
|
||||
const message = await this.build(template, variables)
|
||||
await this.provider.send({
|
||||
to: variables.user.phone,
|
||||
...message,
|
||||
})
|
||||
try {
|
||||
await this.provider.send({
|
||||
to: variables.user.phone,
|
||||
...message,
|
||||
})
|
||||
} catch (error: any) {
|
||||
|
||||
// If for some reason we are getting an unsubscribe error
|
||||
// force unsubscribe the user from this subscription type
|
||||
if (error instanceof UnsubscribeTextError) {
|
||||
unsubscribe(variables.user.id, variables.context.subscription_id)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async build(template: TextTemplate, variables: Variables): Promise<CompiledText> {
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
export default class TextError extends Error {
|
||||
constructor(type: string, message: string) {
|
||||
phone: string
|
||||
constructor(type: string, phone: string, message: string) {
|
||||
super(`Text Error: ${type}: ${message}`)
|
||||
this.phone = phone
|
||||
Error.captureStackTrace(this, TextError)
|
||||
}
|
||||
}
|
||||
|
||||
export class UnsubscribeTextError extends TextError { }
|
||||
|
||||
export class UndeliverableTextError extends TextError { }
|
||||
|
||||
export class RateLimitTextError extends TextError { }
|
||||
|
|
|
@ -41,7 +41,16 @@ export default class TextJob extends Job {
|
|||
const isReady = await prepareSend(channel, data, raw, segments)
|
||||
if (!isReady) return
|
||||
|
||||
await channel.send(template, data)
|
||||
try {
|
||||
await channel.send(template, data)
|
||||
} catch (error: any) {
|
||||
await updateSendState({
|
||||
campaign,
|
||||
user,
|
||||
user_step_id: trigger.user_step_id,
|
||||
state: 'failed',
|
||||
})
|
||||
}
|
||||
|
||||
// Update send record
|
||||
await updateSendState({
|
||||
|
|
|
@ -2,6 +2,7 @@ import App from '../../app'
|
|||
import { encodeHashid } from '../../utilities'
|
||||
import { ExternalProviderParams, ProviderControllers, ProviderSchema, ProviderSetupMeta } from '../Provider'
|
||||
import { createController } from '../ProviderService'
|
||||
import TextError, { UndeliverableTextError, UnsubscribeTextError } from './TextError'
|
||||
import { InboundTextMessage, TextMessage, TextResponse } from './TextMessage'
|
||||
import { TextProvider } from './TextProvider'
|
||||
|
||||
|
@ -50,7 +51,6 @@ export default class TwilioTextProvider extends TextProvider {
|
|||
}
|
||||
|
||||
loadSetup(app: App): ProviderSetupMeta[] {
|
||||
console.log('load setup')
|
||||
return [{
|
||||
name: 'Unsubscribe URL',
|
||||
value: `${app.env.apiBaseUrl}/providers/${encodeHashid(this.id)}/${(this.constructor as any).namespace}/unsubscribe`,
|
||||
|
@ -81,7 +81,14 @@ export default class TwilioTextProvider extends TextProvider {
|
|||
response: responseBody.sid,
|
||||
}
|
||||
} else {
|
||||
throw new Error(`${response.status} - ${responseBody.message}`)
|
||||
if (responseBody.code === 21610) {
|
||||
// Unable to send because recipient has unsubscribed
|
||||
throw new UnsubscribeTextError(this.type, this.phone_number, responseBody.message)
|
||||
} else if (responseBody.code === 21408) {
|
||||
// Unable to send because region is not enabled
|
||||
throw new UndeliverableTextError(this.type, this.phone_number, responseBody.message)
|
||||
}
|
||||
throw new TextError(this.type, this.phone_number, responseBody.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -119,6 +119,7 @@ router.patch('/', projectRoleMiddleware('editor'), async ctx => {
|
|||
})
|
||||
|
||||
const deleteUsersRequest: JSONSchemaType<string[]> = {
|
||||
$id: 'deleteUsers',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
|
|
|
@ -59,7 +59,7 @@ export default function ProjectSidebar({ children, links }: PropsWithChildren<Si
|
|||
value={project}
|
||||
onChange={project => {
|
||||
if (project.id === 0) {
|
||||
navigate('/settings/projects')
|
||||
navigate('/organization/projects')
|
||||
} else {
|
||||
navigate(`/projects/${project.id}`)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue