TEBEX-1618 - Add async launch callback#33
Conversation
…ethod with async callback support for better basket identification handling.
There was a problem hiding this comment.
Pull request overview
This pull request adds support for asynchronously resolving the basket identifier (ident) at checkout launch time via an optional callback parameter to Tebex.checkout.launch(). The implementation ensures the spinner is displayed during the callback execution, handles popup blocking gracefully for mobile flows, and includes comprehensive test coverage for the new functionality.
Changes:
- Made
identoptional inCheckoutOptionsand added async callback parameter tolaunch()method for resolving ident at runtime - Added
launchTimeoutconfiguration option (default 10 seconds) with validation to control callback timeout duration - Implemented loading spinner display during lightbox opening and callback execution, with proper cleanup on iframe ready
Reviewed changes
Copilot reviewed 11 out of 23 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/checkout.ts | Core implementation of async callback support in launch(), timeout handling, and mobile popup pre-opening logic |
| src/components/lightbox.tsx | Added showSpinner() and hideSpinner() methods with lifecycle management |
| src/components/lightbox.css | CSS for spinner animation with keyframe rotation |
| src/utils/timeout.ts | New generic withTimeout() utility for promise timeout handling |
| src/utils/err.ts | Enhanced err() with configurable prefix and added unused logError() utility |
| src/utils/index.ts | Export withTimeout utility |
| src/types/zoid.d.ts | Updated ZoidComponent type signature to accept optional window parameter |
| tests/checkout.test.ts | Comprehensive test coverage for callback scenarios, timeout handling, and error cases |
| tests/index.test-d.ts | Updated type tests to reflect optional ident parameter |
| example/index.js | Updated example to demonstrate async basket fetching at launch time |
| example/dist/index.js | Compiled example code |
| package.json | Version bump to 1.11.0 |
| package-lock.json | Added Node.js engine constraint >= 20.0.0 |
| dist/* | Generated distribution files with new functionality |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/utils/err.ts
Outdated
| export const logError = (msg = "") => { | ||
| console.error("Tebex.js error" + (msg ? ": " : "") + msg.trim()); | ||
| }; |
There was a problem hiding this comment.
The logError utility function is defined but never used in the codebase. Consider removing it unless it's intended for future use. If it's meant for external consumption, it should be exported from the main module index.
| async #openMobilePopupWithCallback(callback: () => Promise<string>) { | ||
| // Must be opened synchronously — browsers block window.open after an async operation. | ||
| const preOpenedWin = window.open('', '_blank'); | ||
|
|
||
| if (preOpenedWin) { | ||
| const spinner = spinnerRender({ props: {} }); | ||
| preOpenedWin.document.open(); | ||
| preOpenedWin.document.write(spinner.outerHTML); | ||
| preOpenedWin.document.close(); | ||
| } | ||
|
|
||
| try { | ||
| await this.#resolveIdentFromCallback(callback); | ||
| } catch (error) { | ||
| preOpenedWin?.close(); | ||
| throw error; | ||
| } | ||
|
|
||
| await this.#createComponentInstance(document.body, true, preOpenedWin ?? undefined); |
There was a problem hiding this comment.
If window.open() fails and returns null (which can happen if popups are blocked), preOpenedWin will be null. The code then passes preOpenedWin ?? undefined to #createComponentInstance. However, the check on line 267 if (preOpenedWin) only writes the spinner HTML if the window was successfully opened. This means if the popup was blocked, the user won't see any feedback, and the code will proceed to call #createComponentInstance with undefined, which may cause zoid to attempt opening a new popup anyway. Consider handling the popup-blocked case more explicitly, perhaps by showing an error message to the user or falling back to a different behavior.
src/components/lightbox.css
Outdated
| display: block !important; | ||
| } | ||
|
|
||
| .tebex-js-lightbox__spinner { |
There was a problem hiding this comment.
Is there any way this could reuse the Zoid spinner? Or at least the styles in spinner.css?
There was a problem hiding this comment.
Will refactor to use the same, I had avoided it because the spinner was a bit awkward to implement at first.
This pull request introduces support for launching the checkout with an asynchronous callback, allowing the basket identifier (
ident) to be resolved at launch time rather than during initialization. It also adds a configurable timeout for this callback, changes the loading spinner to appear while the callback is running.Support for async basket resolution and improved loading experience:
Tebex.checkout.launch()that allows the basket ident to be fetched at launch time, with a configurable timeout (launchTimeout). If the callback is provided, the ident is resolved before the checkout iframe is created. [1] [2] [3] [4] [5] [6] [7] [8] [9]API and type changes:
identinCheckoutOptionsto be optional, allowing it to be resolved at launch time.launchTimeouttoCheckoutOptionsand implemented validation/warning logic for its value. [1] [2] [3]Utility and testing improvements:
withTimeoututility for promise timeout handling. [1] [2]ident,launchTimeoutdefaults, and validation. [1] [2]Error handling improvements:
logErrorutility for improved error logging.Example usage update:
example/index.jsto demonstrate fetching the basket ident asynchronously at checkout launch, and removed pre-fetch logic on page load. [1] [2]