Extracts the Meta JS SDK logic for the WhatsApp embedded-signup and Facebook Page connect flows out of their settings components into two reusable composables. No behavior change — the settings inbox-creation flows work exactly as before; this just makes the SDK orchestration reusable (an upcoming onboarding PR consumes them) and adds unit coverage. ## What changed - `useWhatsappEmbeddedSignup` — owns the embedded-signup popup (SDK load, FB.login, and the auth-code / postMessage race). `WhatsappEmbeddedSignup.vue` now consumes it. - `useFacebookPageConnect` — owns FB.login (page scopes) + page fetch, with SDK preload split from login so the popup opens within the click's activation window. `Facebook.vue` now consumes it. - Adds unit specs for both composables. ## Related PRs - https://github.com/chatwoot/chatwoot/pull/14569 - https://github.com/chatwoot/chatwoot/pull/14568 - https://github.com/chatwoot/chatwoot/pull/14567 - https://github.com/chatwoot/chatwoot/pull/14649 - https://github.com/chatwoot/chatwoot/pull/14565 (Primary onboarding PR)
82 lines
3.0 KiB
JavaScript
82 lines
3.0 KiB
JavaScript
import { ref } from 'vue';
|
|
import { useMapGetter } from 'dashboard/composables/store';
|
|
import ChannelApi from 'dashboard/api/channels';
|
|
import { setupFacebookSdk } from 'dashboard/routes/dashboard/settings/inbox/channels/whatsapp/utils';
|
|
|
|
// Page-management + messaging scopes required to list pages and create a
|
|
// Channel::FacebookPage inbox (mirrors the standalone settings flow).
|
|
const FB_PAGE_SCOPES =
|
|
'pages_manage_metadata,business_management,pages_messaging,instagram_basic,pages_show_list,pages_read_engagement,instagram_manage_messages';
|
|
|
|
// Headless half of the Facebook Page connect flow: load the Meta SDK, run
|
|
// FB.login for page scopes, and fetch the user's pages. The caller owns the
|
|
// page-picker UI and the channel creation, because choosing a page is an
|
|
// interactive step (a user can manage several pages).
|
|
//
|
|
// Split into preloadSdk() + loginAndFetchPages() for popup safety: FB.login
|
|
// opens a popup and needs the click's transient activation. Preloading the SDK
|
|
// when the picker opens means the click-time `await` resolves within that
|
|
// activation window; a cold load resolves on the script's `load` task seconds
|
|
// later, after activation has expired, and the popup gets blocked.
|
|
export function useFacebookPageConnect() {
|
|
const accountId = useMapGetter('getCurrentAccountId');
|
|
const isAuthenticating = ref(false);
|
|
|
|
let sdkSetupPromise = null;
|
|
|
|
// Idempotent — call this when the picker UI opens. A failed load clears the
|
|
// cache so a later attempt can retry instead of being stuck on a rejection.
|
|
const preloadSdk = () => {
|
|
if (!sdkSetupPromise) {
|
|
sdkSetupPromise = setupFacebookSdk(
|
|
window.chatwootConfig?.fbAppId,
|
|
window.chatwootConfig?.fbApiVersion
|
|
).catch(error => {
|
|
sdkSetupPromise = null;
|
|
throw error;
|
|
});
|
|
}
|
|
return sdkSetupPromise;
|
|
};
|
|
|
|
// FB.login never rejects; resolve the user access token on success and null
|
|
// for any other status (closed popup, not_authorized, unknown).
|
|
const login = () =>
|
|
new Promise(resolve => {
|
|
window.FB.login(
|
|
response => {
|
|
resolve(
|
|
response.status === 'connected'
|
|
? response.authResponse?.accessToken || null
|
|
: null
|
|
);
|
|
},
|
|
{ scope: FB_PAGE_SCOPES }
|
|
);
|
|
});
|
|
|
|
// Resolves { userAccessToken, pages } on success, null when the user cancels,
|
|
// and rejects on SDK-load or page-fetch failure (the caller maps it to UI).
|
|
const loginAndFetchPages = async () => {
|
|
if (isAuthenticating.value) return null;
|
|
isAuthenticating.value = true;
|
|
try {
|
|
await preloadSdk();
|
|
const token = await login();
|
|
if (!token) return null;
|
|
|
|
const response = await ChannelApi.fetchFacebookPages(
|
|
token,
|
|
accountId.value
|
|
);
|
|
const { page_details: pages, user_access_token: userAccessToken } =
|
|
response.data.data;
|
|
return { userAccessToken, pages };
|
|
} finally {
|
|
isAuthenticating.value = false;
|
|
}
|
|
};
|
|
|
|
return { isAuthenticating, preloadSdk, loginAndFetchPages };
|
|
}
|