Skip to content

Commit b91823b

Browse files
authored
feat: adding callback server for peer-to-peer example (#18)
* feat: adding callback server for OP finish call * feat(peer-to-peer): resolve interactRef in a promise * feat(peer-to-peer): adding metadata description to payments * chore(peer-to-peer): formatting & cleaning up
1 parent f29e4d7 commit b91823b

File tree

3 files changed

+479
-21
lines changed

3 files changed

+479
-21
lines changed

examples/peer-to-peer/index.js

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
isFinalizedGrant
1515
} from '@interledger/open-payments'
1616
import readline from 'readline/promises'
17+
import express from 'express'
1718
;(async () => {
1819
// Client configuration
1920
const PRIVATE_KEY_PATH = 'private.key'
@@ -81,6 +82,9 @@ import readline from 'readline/promises'
8182
assetCode: receivingWalletAddress.assetCode,
8283
assetScale: receivingWalletAddress.assetScale,
8384
value: '1000'
85+
},
86+
metadata: {
87+
description: 'From peer-to-peer example script'
8488
}
8589
}
8690
)
@@ -128,6 +132,8 @@ import readline from 'readline/promises'
128132

129133
console.log('\nStep 5: got quote on sending wallet address', quote)
130134

135+
const callbackServerPort = 3999
136+
131137
// Step 7: Start the grant process for the outgoing payments.
132138
// This is an interactive grant: the user (in this case, you) will need to accept the grant by navigating to the outputted link.
133139
const outgoingPaymentGrant = await client.grant.request(
@@ -152,15 +158,14 @@ import readline from 'readline/promises'
152158
]
153159
},
154160
interact: {
155-
start: ['redirect']
156-
// finish: {
157-
// method: "redirect",
158-
// // This is where you can (optionally) redirect a user to after going through interaction.
159-
// // Keep in mind, you will need to parse the interact_ref in the resulting interaction URL,
160-
// // and pass it into the grant continuation request.
161-
// uri: "https://example.com",
162-
// nonce: crypto.randomUUID(),
163-
// },
161+
start: ['redirect'],
162+
finish: {
163+
method: 'redirect',
164+
// The uri is where the user is redirected to after going through interaction with their wallet/identity provider. For this example, we use a temporary HTTP server to handle the redirect.
165+
uri: `http://localhost:${callbackServerPort}`,
166+
// The nonce is used as part of hash verification when redirecting to the uri. Please visit https://openpayments.dev/identity/hash-verification/ for more details.
167+
nonce: crypto.randomUUID()
168+
}
164169
}
165170
}
166171
)
@@ -174,6 +179,10 @@ import readline from 'readline/promises'
174179
)
175180
console.log(outgoingPaymentGrant.interact.redirect)
176181

182+
const interactRef = await getInteractRefFromTempCallbackServer(
183+
callbackServerPort
184+
)
185+
177186
await readline
178187
.createInterface({ input: process.stdin, output: process.stdout })
179188
.question('\nPlease accept grant and press enter...')
@@ -184,10 +193,13 @@ import readline from 'readline/promises'
184193
'\nThere was an error continuing the grant. You probably have not accepted the grant at the url (or it has already been used up, in which case, rerun the script).'
185194

186195
try {
187-
finalizedOutgoingPaymentGrant = await client.grant.continue({
188-
url: outgoingPaymentGrant.continue.uri,
189-
accessToken: outgoingPaymentGrant.continue.access_token.value
190-
})
196+
finalizedOutgoingPaymentGrant = await client.grant.continue(
197+
{
198+
url: outgoingPaymentGrant.continue.uri,
199+
accessToken: outgoingPaymentGrant.continue.access_token.value
200+
},
201+
{ interact_ref: interactRef }
202+
)
191203
} catch (err) {
192204
if (err instanceof OpenPaymentsClientError) {
193205
console.log(grantContinuationErrorMessage)
@@ -218,7 +230,10 @@ import readline from 'readline/promises'
218230
},
219231
{
220232
walletAddress: sendingWalletAddress.id,
221-
quoteId: quote.id
233+
quoteId: quote.id,
234+
metadata: {
235+
description: 'Sent from peer-to-peer example script'
236+
}
222237
}
223238
)
224239

@@ -229,3 +244,39 @@ import readline from 'readline/promises'
229244

230245
process.exit()
231246
})()
247+
248+
/**
249+
* Starts a temporary local HTTP server to handle Open Payments Auth Server callback redirects, and return the resulting interact ref.
250+
*/
251+
async function getInteractRefFromTempCallbackServer(port) {
252+
return new Promise((resolve) => {
253+
let server
254+
const app = express()
255+
256+
app.get('/', async (req, res) => {
257+
const interactRef = req.query['interact_ref']
258+
259+
res.send(`
260+
<html>
261+
<body style="font-family: monospace; padding: 2rem; text-align: center;">
262+
<img src="https://raw.githubusercontent.com/interledger/open-payments/main/docs/public/img/logo.svg" width="300" alt="Open Payments" style="max-width: 100%; margin-bottom: 2rem;">
263+
<h1>Authentication successful</h1>
264+
<p>You can close this window and return to your terminal.</p>
265+
</body>
266+
</html>
267+
`)
268+
269+
server.close()
270+
resolve(interactRef)
271+
})
272+
273+
server = app.listen(port).on('error', (err) => {
274+
if (err.code === 'EADDRINUSE') {
275+
console.error(
276+
`Port ${port} is already in use, please select a new port for the callback server.`
277+
)
278+
process.exit(1)
279+
}
280+
})
281+
})
282+
}

examples/peer-to-peer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"license": "ISC",
1414
"dependencies": {
1515
"@interledger/open-payments": "workspace:^",
16+
"express": "^5.1.0",
1617
"readline": "^1.3.0"
1718
}
1819
}

0 commit comments

Comments
 (0)