-
Notifications
You must be signed in to change notification settings - Fork 2
🥅 server: bridge credential fallback and retry on timeout #896
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/server": patch | ||
| --- | ||
|
|
||
| 🥅 add bridge credential fallback and retry on timeout |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import { vValidator } from "@hono/valibot-validator"; | ||
| import { captureException, setUser } from "@sentry/core"; | ||
| import { captureEvent, captureException, setUser } from "@sentry/core"; | ||
| import createDebug from "debug"; | ||
| import { eq } from "drizzle-orm"; | ||
| import { and, DrizzleQueryError, eq, isNull } from "drizzle-orm"; | ||
| import { Hono } from "hono"; | ||
| import { validator } from "hono/validator"; | ||
| import { createHash, createVerify } from "node:crypto"; | ||
|
|
@@ -11,7 +11,8 @@ import { Address } from "@exactly/common/validation"; | |
|
|
||
| import database, { credentials } from "../database"; | ||
| import { sendPushNotification } from "../utils/onesignal"; | ||
| import { BridgeCurrency, publicKey } from "../utils/ramps/bridge"; | ||
| import { searchAccounts } from "../utils/persona"; | ||
| import { BridgeCurrency, getCustomer, publicKey } from "../utils/ramps/bridge"; | ||
| import { track } from "../utils/segment"; | ||
| import validatorHook from "../utils/validatorHook"; | ||
|
|
||
|
|
@@ -98,10 +99,57 @@ export default new Hono().post( | |
| payload.event_type === "customer.updated.status_transitioned" | ||
| ? payload.event_object.id | ||
| : payload.event_object.customer_id; | ||
| const credential = await database.query.credentials.findFirst({ | ||
| let credential = await database.query.credentials.findFirst({ | ||
| columns: { account: true, source: true }, | ||
| where: eq(credentials.bridgeId, bridgeId), | ||
| }); | ||
| if (!credential && payload.event_type === "customer.updated.status_transitioned") { | ||
| credential = await getCustomer(bridgeId) | ||
| .then((customer) => (customer ? searchAccounts(customer.email) : undefined)) | ||
| .then((accounts) => { | ||
| if (accounts && accounts.length > 1) | ||
| captureException(new Error("multiple persona accounts found"), { | ||
| level: "fatal", | ||
| contexts: { details: { bridgeId, matches: accounts.length } }, | ||
| }); | ||
| return accounts?.length === 1 ? accounts[0]?.attributes["reference-id"] : undefined; | ||
mainqueg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) | ||
| .then((referenceId) => | ||
| referenceId | ||
| ? database | ||
| .update(credentials) | ||
| .set({ bridgeId }) | ||
| .where(and(eq(credentials.id, referenceId), isNull(credentials.bridgeId))) | ||
| .returning({ account: credentials.account, source: credentials.source }) | ||
| .then(([updated]) => { | ||
| if (!updated) throw new Error("credential already paired"); | ||
|
Comment on lines
+124
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Nonexistent credential misreported as "already paired" with fatal severity When the persona This conflates two distinct failure modes: (a) the credential exists but already has a Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| captureEvent({ | ||
| message: "bridge credential paired", | ||
| level: "warning", | ||
| contexts: { details: { bridgeId, referenceId } }, | ||
| }); | ||
| return updated; | ||
| }) | ||
| .catch((error: unknown): undefined => { | ||
| if ( | ||
| !( | ||
| (error instanceof Error && error.message === "credential already paired") || | ||
| (error instanceof DrizzleQueryError && | ||
| error.cause && | ||
| "code" in error.cause && | ||
| error.cause.code === "23505") | ||
| ) | ||
| ) | ||
| throw error; | ||
| captureEvent({ | ||
| message: "bridge credential already paired", | ||
| level: "fatal", | ||
| contexts: { details: { bridgeId } }, | ||
| }); | ||
| }) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| : undefined, | ||
| ); | ||
|
Comment on lines
+106
to
+151
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚩 Fallback chain errors propagate to Hono's error handler The fallback logic in Was this helpful? React with 👍 or 👎 to provide feedback.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not relevant, as it is the intended behavior |
||
| } | ||
| if (!credential) { | ||
| captureException(new Error("credential not found"), { | ||
| level: "error", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a
customer.updated.status_transitionedwebhook arrives for an unpaired Bridge customer, this branch will persistbridgeIdbefore we inspectpayload.event_object.status. That is dangerous for terminal statuses likerejected,paused, oroffboarded: once the credential is paired,bridge.onboarding()immediately returnsALREADY_ONBOARDEDfor any non-nullcustomerId(server/utils/ramps/bridge.ts:299-300), andgetProvider()maps those same statuses toNOT_AVAILABLE(server/utils/ramps/bridge.ts:184-193). In other words, a rejected/offboarded webhook that used to be ignored now permanently blocks the user from starting a fresh Bridge onboarding flow.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a fallback for the user creation endpoint, doesn't matter the customer status