From a0d023e10ecd6db16ff4ac8df25893bb77d56012 Mon Sep 17 00:00:00 2001 From: Wietse Wind Date: Wed, 9 Nov 2022 02:15:19 +0100 Subject: [PATCH] Add event based --- sample/index.html | 150 +++++++++++++++++++++++++++++++++------------- src/index.ts | 59 ++++++++++++++---- 2 files changed, 154 insertions(+), 55 deletions(-) diff --git a/sample/index.html b/sample/index.html index 17f9538..9e06014 100644 --- a/sample/index.html +++ b/sample/index.html @@ -14,19 +14,26 @@ - +

Hello, world!

- This is Web3 wallet identity & transaction initiation for the XRP Ledger ecosystem. -
+ This is + Web3 wallet identity & transaction initiation + for the XRP Ledger ecosystem. +
Tech under the hood: OAuth2 Implicit PKCE flow for the Xumm platform and XRP Ledger ecosystem. - Using nothing but simple client side Javascript: source. + Using nothing but simple client side Javascript: source + .

@@ -45,7 +52,9 @@

Hello, world!

- Source on Github + + Source on Github +
@@ -56,13 +65,26 @@

Hello, world!

var auth = new XummPkce('47d328db-0b34-4451-a258-393480c9b4cd') var sdk = null - auth.on('retrieved', event => { + auth.on('error', error => { + alert( + 'error: ' + error + ?.message) + }) + + auth.on('success', async () => { + alert('success: ' + JSON.stringify((await auth.state()).me)) + }) + + auth.on('retrieved', async () => { // Redirect, e.g. mobile. Mobile may return to new tab, this // handles the same logic (re-inits the auth Promise) normally // triggered by e.g. a button. // > Note: it emulates without opening another auth window ;) console.log('Results are in, mobile flow, emulate auth trigger') + go() + + alert('retrieved: ' + JSON.stringify((await auth.state()).me)) }) /** @@ -72,11 +94,26 @@

Hello, world!

function reset() { signinbtn.innerText = 'Sign in' - document.getElementById('signedin').style.display = 'none' - document.getElementById('error').style.display = 'none' - document.getElementById('trypayload').style.display = 'none' - document.getElementById('logout').style.display = 'none' - document.getElementById('results').style.display = 'none' + document + .getElementById('signedin') + .style + .display = 'none' + document + .getElementById('error') + .style + .display = 'none' + document + .getElementById('trypayload') + .style + .display = 'none' + document + .getElementById('logout') + .style + .display = 'none' + document + .getElementById('results') + .style + .display = 'none' } // Start in default UI state @@ -93,31 +130,51 @@

Hello, world!

.authorize() .then(authorized => { // Assign to global, please don't do this but for the sake of the demo it's easy - sdk = authorized.sdk + sdk = authorized + .sdk - console.log('Authorized', /* authorized.jwt, */ authorized.me) + console + .log('Authorized', + /* authorized.jwt, */ + authorized.me) signinbtn.style.display = 'none' - document.getElementById('signedin').style.display = 'block' + document + .getElementById('signedin') + .style + .display = 'block' var resultspre = document.getElementById('results') resultspre.style.display = 'block' resultspre.innerText = JSON.stringify(authorized.me, null, 2) - document.getElementById('trypayload').style.display = 'block' - document.getElementById('logout').style.display = 'block' - - sdk.ping().then(pong => console.log({pong})) + document + .getElementById('trypayload') + .style + .display = 'block' + document + .getElementById('logout') + .style + .display = 'block' + + sdk + .ping() + .then(pong => console.log({pong})) }) .catch(e => { console.log('Auth error', e) reset() - document.getElementById('error').style.display = 'block' - document.getElementById('error').innerText = e.message + document + .getElementById('error') + .style + .display = 'block' + document + .getElementById('error') + .innerText = e.message }) - } + } function go_logout() { auth.logout() @@ -127,7 +184,7 @@

Hello, world!

function go_payload() { /** - * xumm-oauth2-pkce package returns `sdk` property, + * xumm-oauth2-pkce package returns `sdk` property, * allowing access to the Xumm SDK (`xumm-sdk`) package. * Xumm SDK methods, docs: * https://www.npmjs.com/package/xumm-sdk @@ -140,26 +197,33 @@

Hello, world!

} } - sdk.payload.createAndSubscribe(payload, function (payloadEvent) { - if (typeof payloadEvent.data.signed !== 'undefined') { - // What we return here will be the resolved value of the `resolved` property - return payloadEvent.data - } - }).then(function ({created, resolved}) { - if (created.pushed) { - alert('Now check Xumm, there should be a push notification + sign request in your event list waiting for you ;)') - } else { - alert('Now check Xumm, there should be a sign request in your event list waiting for you ;) (This would have been pushed, but it seems you did not grant Xumm the push permission)') - } - - resolved.then(function (payloadOutcome) { - alert('Payload ' + (payloadOutcome.signed ? 'signed (TX Hash: ' + payloadOutcome.txid + ')' : 'rejected') + ', see the browser console for more info') - console.log(payloadOutcome) + sdk + .payload + .createAndSubscribe(payload, function (payloadEvent) { + if (typeof payloadEvent.data.signed !== 'undefined') { + // What we return here will be the resolved value of the `resolved` property + return payloadEvent.data + } }) - }).catch(function (payloadError) { - alert('Paylaod error', e.message) - }) - } + .then(function ({created, resolved}) { + if (created.pushed) { + alert('Now check Xumm, there should be a push notification + sign request in your event list waiting for you ;)') + } else { + alert('Now check Xumm, there should be a sign request in your event list waiting for you ;) (This would have been pushed, but it seems you did not grant Xumm the push permission)') + } + + resolved.then(function (payloadOutcome) { + alert('Payload ' + ( + payloadOutcome.signed + ? 'signed (TX Hash: ' + payloadOutcome.txid + ')' + : 'rejected') + ', see the browser console for more info') + console.log(payloadOutcome) + }) + }) + .catch(function (payloadError) { + alert('Paylaod error', e.message) + }) + } - + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ae04bed..bdb09de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ interface XummPkceOptions { rememberJwt: boolean; storage: Storage; } + interface ResolvedFlow { sdk: XummSdkJwt; jwt: string; @@ -37,9 +38,9 @@ interface ResolvedFlow { } export interface XummPkceEvent { - // `retrieved` returns nothing, just a trigger, the authorize() - // method should be called later to handle based on Promise() retrieved: () => void; + error: (error: Error) => void; + success: () => void; } export declare interface XummPkce { @@ -126,7 +127,7 @@ export class XummPkce extends EventEmitter { if (existingJwt?.jwt && typeof existingJwt.jwt === "string") { const sdk = new XummSdkJwt(existingJwt.jwt); - sdk.ping().then((pong) => { + sdk.ping().then(async (pong) => { /** * Pretend mobile so no window.open is triggered */ @@ -271,7 +272,7 @@ export class XummPkce extends EventEmitter { this.mobileRedirectFlow = true; this.urlParams = params; - document.addEventListener("readystatechange", (event) => { + document.addEventListener("readystatechange", async (event) => { if (document.readyState === "complete") { log("(readystatechange: [ " + document.readyState + " ])"); this.handleMobileGrant(); @@ -287,9 +288,9 @@ export class XummPkce extends EventEmitter { } private handleMobileGrant() { - // log(document?.location?.search); if (this.urlParams && this.mobileRedirectFlow) { log("Send message event"); + const messageEventData = { data: JSON.stringify( this.urlParams.get("authorization_code") @@ -311,9 +312,9 @@ export class XummPkce extends EventEmitter { origin: "https://oauth2.xumm.app", }; - // log(messageEventData); const event = new MessageEvent("message", messageEventData); window.dispatchEvent(event); + return true; } return false; @@ -336,22 +337,51 @@ export class XummPkce extends EventEmitter { this.resolved = false; + const clearUrlParams = () => { + if (this.urlParams && this.mobileRedirectFlow) { + const newUrlParams = new URLSearchParams( + document?.location?.search || "" + ); + newUrlParams.delete("authorization_code"); + newUrlParams.delete("code"); + newUrlParams.delete("state"); + const newSearchParamsString = newUrlParams.toString(); + + history.replaceState( + {}, + "", + document.location.href.split("?")[0] + + (newSearchParamsString !== "" ? "?" : "") + + newSearchParamsString + ); + } + }; + + clearUrlParams(); + if (this.autoResolvedFlow) { - this.resolved = true; - this.promise = Promise.resolve(this.autoResolvedFlow); - this.rejectPromise = this.resolvePromise = () => {}; - log("Auto resolved"); + if (!this.resolved) { + this.resolved = true; + this.promise = Promise.resolve(this.autoResolvedFlow); + this.rejectPromise = this.resolvePromise = () => {}; + log("Auto resolved"); + this.emit("success"); + } } else { this.promise = new Promise((resolve, reject) => { this.resolvePromise = (_) => { + const resolved = resolve(_); this.resolved = true; log("Xumm Sign in RESOLVED"); - return resolve(_); + this.emit("success"); + return resolved; }; this.rejectPromise = (_) => { + const rejected = reject(_); this.resolved = true; + this.emit("error", typeof _ === "string" ? new Error(_) : _); log("Xumm Sign in REJECTED"); - return reject(_); + return rejected; }; }); } @@ -359,8 +389,13 @@ export class XummPkce extends EventEmitter { return this.promise; } + public async state() { + return this.promise; + } + public logout() { try { + this.resolved = false; this.autoResolvedFlow = undefined; this.options.storage?.removeItem("XummPkceJwt"); } catch (e) {