Connector Widget¶
The connector widget is a self-contained Vue 3 custom element (<conflux-connector>) that an embedding platform (e.g. Rapidmail) drops onto its own pages to let users connect their e-commerce store to Conflux — without ever leaving the embedding platform's UI.
It is distinct from the configuration widget (<conflux-config>): the configuration widget manages sync settings for an already-established connection; the connector widget handles the connection establishment itself.
Why a custom element¶
The widget is built with Vue 3's defineCustomElement and compiled to a single IIFE bundle (connector-widget.js). Embedding it requires one <script> tag and one HTML element — no npm install, no framework requirement, no style leakage in either direction thanks to the shadow DOM boundary.
Two-phase flow¶
A typical embed goes through two phases in sequence.
Phase 1 — Initialisation¶
On mount, if no tenant-id prop is present, the widget immediately calls POST /api/system-connections/connect with system_slug and external_user_id in the request body.
When the system_slug matches a known System record in Conflux (e.g. rapidmail), the endpoint:
- Auto-creates a new
Tenantwith a generated name. - Creates a
SystemConnectionlinked to that tenant withstatus = activeandlast_connected_atstamped immediately — no plugin handshake is needed because the embedding system is Conflux itself. - Issues a never-expiring
SystemConnectionAccessTokenand returns it astokenin the response alongsidetenant_id.
The widget stores both values internally and fires the conflux-initialized event so the host page can persist them. Future page loads should pass tenant-id and token back as props, which skips phase 1 entirely.
sequenceDiagram
participant Widget as <conflux-connector>
participant Conflux
participant HostPage as Host Page (JS)
Widget->>Conflux: POST /api/system-connections/connect<br/>{ system_slug: "rapidmail", external_user_id: "..." }
Conflux->>Conflux: create Tenant + active SystemConnection<br/>issue SystemConnectionAccessToken
Conflux-->>Widget: 201 { tenant_id, token, connection }
Widget->>HostPage: dispatchEvent("conflux-initialized")<br/>{ tenant_id, token }
Note over HostPage: persist tenant_id + token<br/>for future page loads
Phase 2 — Plugin pairing¶
Once initialised, the widget shows the user platform-specific instructions and a "Generate connection token" button. When clicked, the widget calls POST /api/system-connections/connect again, this time:
- With
Authorization: Bearer <access-token>so the server resolves the tenant from the token rather than from a body param. - With an empty body (or
{ tenant_id }as fallback if no access token is available).
The endpoint creates a new SystemConnection with status = pending and returns a short-lived connection_token for the user to paste into their e-commerce plugin.
The widget then begins polling GET /api/widget/connections/{connection_id}/status every 3 seconds. When the plugin completes its side of the handshake and Conflux marks the connection active, the widget stops polling, shows a success banner, and fires the conflux-registered event.
sequenceDiagram
participant Widget as <conflux-connector>
participant Conflux
participant Plugin as E-commerce Plugin
participant HostPage as Host Page (JS)
Widget->>Conflux: POST /api/system-connections/connect<br/>Authorization: Bearer <access-token>
Conflux->>Conflux: resolve tenant from token<br/>create pending SystemConnection<br/>issue short-lived connection_token
Conflux-->>Widget: 201 { connection_id, connection_token, expires_at }
Widget->>Widget: show token + start polling
loop every 3 s
Widget->>Conflux: GET /api/widget/connections/{id}/status<br/>Authorization: Bearer <access-token>
Conflux-->>Widget: { id, status: "pending" }
end
Note over Plugin: merchant pastes token into plugin
Plugin->>Conflux: POST /api/system-connections/register<br/>connection_token + credentials
Conflux->>Conflux: verify + activate connection
Widget->>Conflux: GET /api/widget/connections/{id}/status
Conflux-->>Widget: { id, status: "active" }
Widget->>Widget: stop polling, show success
Widget->>HostPage: dispatchEvent("conflux-registered")<br/>{ connection_id }
Access tokens vs connection tokens¶
Two different token types are involved.
SystemConnectionAccessToken |
SystemConnectionToken |
|
|---|---|---|
| Table | system_connection_access_tokens |
system_connection_tokens |
| Issued when | Phase 1 init (Active direct-system connection) | Phase 2 "Generate token" button |
| Lifetime | Never expires | 1 hour (configurable) |
| Purpose | Authenticate subsequent widget API calls | Plugin-pairing handshake (one-time use) |
| Returned to | Host page via conflux-initialized |
User (shown in widget UI for copy-paste) |
The access token is returned in the conflux-initialized event detail only once. The host page must persist it (e.g. in its own database keyed to the user). If lost, the user must re-initialise the widget to obtain a new one.
Events¶
Both events use bubbles: true, composed: true so they pierce the shadow DOM boundary and can be caught on window or any ancestor element.
Events are dispatched from the host element itself (this.$el.getRootNode().host), falling back to this.$el when no shadow root host is present. This matters for Shadow DOM consumers that attach listeners directly to the <conflux-connector> element rather than to window.
conflux-initialized¶
window.addEventListener('conflux-initialized', (event) => {
const { tenant_id, token } = event.detail;
// Persist these; pass them as props on subsequent loads.
});
Fires once at the end of phase 1, when the widget has created (or confirmed) a tenant and an active connection to the embedding system. The token value is the never-expiring access token and is returned only this once.
conflux-registered¶
window.addEventListener('conflux-registered', (event) => {
const { connection_id } = event.detail;
// The e-commerce plugin is now connected.
});
Fires when polling detects status = active on the pending connection — i.e. when the e-commerce plugin has completed its side of the pairing handshake.
Widget phases¶
Internally the widget moves through three display phases:
| Phase | Trigger | UI |
|---|---|---|
initializing |
On mount, no tenant-id prop present |
Spinner; error state with retry if the init call fails |
instructing |
Init completed (or tenant-id prop was already set) |
Plugin CTA, generate-token button, token display, paste instructions, "What's next" panel, polling status |
connected |
Only for the direct-system init (phase 1 created an Active connection with no plugin) | Success banner immediately — no phase 2 needed |
The connected phase is only reached via phase 1 when the system_slug maps to a direct system (like rapidmail). For e-commerce plugin connections, the instructing phase persists while polling waits for the plugin.
UX sections in the instructing phase¶
The instructing phase renders several distinct sections.
Intro and plugin CTA. A short description ("You'll need the Positive Connector plugin…") appears above the token area. A "Get the Positive Connector plugin" external link is always rendered below it. When PLUGIN_URLS[connection][system] resolves to a non-null URL the link goes to that URL; otherwise it falls back to href="../" (effectively a no-op). Set a real URL in plugin-urls.js to activate the link for a given combination.
Token area. The "Generate connection token" button triggers phase 2. Once a token is generated, it is displayed in a read-only copy-paste input alongside its expiry time. A "Generate a new token" link allows re-generation.
"Where do I paste this?" disclosure. A labelled section below the token area lists the paste steps for the active connection type. Steps are driven by the PASTE_STEPS configuration object in the source file and rendered as a numbered list with bold navigation labels. A generic fallback is shown when connection does not match a known key.
Success banner. When connected becomes true (polling detects status: active), a green "Plugin connected successfully!" banner is shown within the instructing phase.
"What's next" disclosure. A two-state info block:
| Widget state | Text shown |
|---|---|
Not yet connected (connected === false) |
Muted: "Once connected, we'll sync your data based on the default settings…" |
Connected (connected === true) |
Active: "We're syncing your data based on the default settings…" + settings breadcrumb from SETTINGS_PATH[connection] |
The settings breadcrumb (e.g. WooCommerce → Positive Connector → Sync settings) is driven by the SETTINGS_PATH configuration object and is hidden when the key is absent.
Configuration constants¶
Two constants at the top of ConfluxConnector.vue and one in a companion file are the intended customisation points for new embedding systems or shop platforms:
| Constant | Location | Keyed by | Purpose |
|---|---|---|---|
PLUGIN_URLS |
resources/widget-connector/plugin-urls.js |
connection → system |
Plugin download URL per shop platform and embedding system. Use default as the fallback key. null hides the CTA. Baked in at build time by Vite. |
PASTE_STEPS |
ConfluxConnector.vue |
connection |
Numbered HTML steps for the "Where do I paste this?" section. Falls back to GENERIC_PASTE_STEPS when the key is absent. |
SETTINGS_PATH |
ConfluxConnector.vue |
connection |
Admin breadcrumb string shown in the connected "What's next" state. Hidden when the key is absent. |
PLUGIN_URLS was deliberately separated into its own file so it can be updated without touching the Vue component. The structure inside plugin-urls.js is:
{
woocommerce: {
default: null, // fallback when system prop does not match any key
rapidmail: null, // embedding-system-specific URL
},
prestashop: {
default: null,
rapidmail: null,
},
}
Set any value to a non-null string to enable the "Get the Positive Connector plugin" CTA link for that combination. Rebuild the bundle after editing this file.
Widget polling¶
Polling uses GET /api/widget/connections/{connection}/status inside the widget-auth middleware group. The route is protected by AuthenticateWidgetToken, which resolves the SystemConnection from the Bearer access token and then checks that the requested connection belongs to the same tenant before returning its status. The widget stops polling as soon as it receives status: active or when the component is unmounted.
Build¶
This runs vite build --config vite.connector-widget.config.js and writes the IIFE bundle to public/connector-widget.js. Run this after editing plugin-urls.js or any other file under resources/widget-connector/ — Vite bakes those values in at build time.
Source files¶
| File | Purpose |
|---|---|
resources/widget-connector/ConfluxConnector.vue |
Vue 3 custom element — template, phase logic, polling, event dispatch, scoped styles |
resources/widget-connector/plugin-urls.js |
Plugin download URL map (connection → system → URL); edit this to activate the CTA link |
resources/widget-connector/main.js |
Entry point — calls defineCustomElement and registers <conflux-connector> |
vite.connector-widget.config.js |
Vite IIFE build config; outputs public/connector-widget.js |
app/Http/Controllers/Api/ConnectSystemConnectionController.php |
POST /api/system-connections/connect — both init and generate-token paths |
app/Http/Controllers/Api/Widget/ConnectionStatusController.php |
GET /api/widget/connections/{connection}/status |
app/Http/Middleware/AuthenticateWidgetToken.php |
Bearer token resolution for widget routes |
resources/views/configuration-widget.blade.php |
Interactive dev playground (two-step setup form + event log) |
resources/views/widget-connector-preview.blade.php |
Minimal standalone preview page |
See also¶
- Embed the Connector Widget — step-by-step embedding guide with full examples
- Connection Widget — the configuration widget (
<conflux-config>) used after a connection is established - Connection Flow — the plugin registration handshake that phase 2 completes
- System Connection Tokens — token hashing and storage