Skip to content
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

feat(unlock-app): Checkout hooks #15234

Open
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

iMac7
Copy link
Contributor

@iMac7 iMac7 commented Dec 3, 2024

Description

Issues

Fixes #15067
Refs #

Checklist:

  • 1 PR, 1 purpose: my Pull Request applies to a single purpose
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the docs to reflect my changes if applicable
  • I have added tests (and stories for frontend components) that prove my fix is effective or that my feature works
  • I have performed a self-review of my own code
  • If my code involves visual changes, I am adding applicable screenshots to this thread

Release Note Draft Snippet

https://www.loom.com/share/3ef701f86f8a4cea9398d05e515be7a6?sid=ef35bc2e-1fc9-4ce6-9479-87b93da5d7bc

@cla-bot cla-bot bot added the cla-signed label Dec 3, 2024
if (!url) return

if (JSON.stringify(body) === prevBody) {
console.log('DUPLICATE REQUEST!! Skipping...', event, body)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please clean this up.

Comment on lines 117 to 147
<fieldset className="flex flex-col gap-4">
<legend>Hooks</legend>
<p className="text-sm text-gray-600">
Configure URLs for specific events during the checkout process
</p>
<Input
label="Status"
size="small"
description="URL to be called on status change"
{...register('hooks.status')}
/>
<Input
label="Authenticated"
size="small"
description="URL to be called when the user is authenticated"
{...register('hooks.authenticated')}
/>
<Input
label="Transaction Sent"
size="small"
description="URL to be called when a transaction is sent"
{...register('hooks.transactionSent')}
/>
<Input
label="Metadata"
size="small"
description="URL to be called for metadata updates"
{...register('hooks.metadata')}
/>
</fieldset>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do add this. This should be a "developer" only setting and I worry this may complicate things for non-dev users. Hooks should only be "configurable" with the API/

Copy link
Member

@julien51 julien51 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit intrigued about notifications. Can you explain?

@iMac7
Copy link
Contributor Author

iMac7 commented Dec 11, 2024

I am a bit intrigued about notifications. Can you explain?

must've overlooked this as toast notifications instead of the one in notification menu, adding 👍

Copy link

vercel bot commented Dec 11, 2024

@iMac7 is attempting to deploy a commit to the Unlock Protocol Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

vercel bot commented Dec 11, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
unlock-storybook ⬜️ Ignored (Inspect) Visit Preview Dec 17, 2024 6:38pm

@iMac7
Copy link
Contributor Author

iMac7 commented Dec 11, 2024

Comment on lines 76 to 145

const saveToLocalStorage = (data: any) => {
const hooks = localStorage.getItem('hooks')
const parsed = (hooks && JSON.parse(hooks)) ?? []
localStorage.setItem(
'hooks',
JSON.stringify([...parsed, { id: parsed.length, ...data }])
)
}

let prevBody: string | null = null
export const postToWebhook = async (body: any, config: any, event: string) => {
const url = config?.hooks && config.hooks[event]

if (!url) return

if (JSON.stringify(body) === prevBody) {
return
}

prevBody = JSON.stringify(body)

const sendWebhookRequest = async (attempt: number) => {
await axios.post(url, body)
const text = `Sent ${event} event data to ${url} on attempt ${attempt}`
toast.success(text)

const data = { text, created: new Date().toISOString(), success: true }
saveToLocalStorage(data)
}

const retryRequest = async (maxRetries: number) => {
let lastError: any
let attempt = 0

while (attempt < maxRetries) {
try {
attempt++
await sendWebhookRequest(attempt)
return
} catch (error) {
lastError = error
toast.error(
`Could not post ${event} event data to ${url}. Attempt ${attempt}`
)
if (attempt < maxRetries) {
const delay = attempt === 1 ? 1000 : 3000
await new Promise((resolve) => setTimeout(resolve, delay))
}
}
}

setTimeout(() => {
if (lastError && attempt === maxRetries) {
const text = `Failed to post ${event} event data to ${url}`
toast.error(text)
const data = { text, created: new Date().toISOString(), success: false }
saveToLocalStorage(data)
}
}, 1000)

throw lastError
}

try {
await retryRequest(3)
} catch (error) {
console.error('Webhook request failed:', error)
}
}
Copy link
Contributor

@searchableguy searchableguy Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a security issue as well as reliability. No calls should be made to user given urls from frontend. It should go through a proxy and have some sort of verification.

Also exponential backoff is preferred in cases such as this and should account for failure by storing the request data in backend. This should be easy to add as a worker to graphile-worker in locksmith/workers directory.

@iMac7
Copy link
Contributor Author

iMac7 commented Dec 17, 2024

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a webhook to checkout
3 participants