Files
chatwoot/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
T

360 lines
9.8 KiB
JavaScript

import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import Auth from 'dashboard/api/auth';
export function useWhatsappEmbeddedSignup() {
const store = useStore();
const router = useRouter();
const { t } = useI18n();
// State
const fbSdkLoaded = ref(false);
const isProcessing = ref(false);
const processingMessage = ref('');
const authCodeReceived = ref(false);
const authCode = ref(null);
const businessData = ref(null);
const isAuthenticating = ref(false);
// Computed
const authHeaders = computed(() => {
if (Auth.hasAuthCookie()) {
const {
'access-token': accessToken,
'token-type': tokenType,
client,
expiry,
uid,
} = Auth.getAuthData();
return {
'access-token': accessToken,
'token-type': tokenType,
client,
expiry,
uid,
};
}
return {};
});
const benefits = computed(() => [
{
key: 'EASY_SETUP',
text: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.BENEFITS.EASY_SETUP'),
},
{
key: 'SECURE_AUTH',
text: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.BENEFITS.SECURE_AUTH'),
},
{
key: 'AUTO_CONFIG',
text: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.BENEFITS.AUTO_CONFIG'),
},
]);
const showLoader = computed(
() => isAuthenticating.value || isProcessing.value
);
// Error handling
const handleSignupError = data => {
isProcessing.value = false;
authCodeReceived.value = false;
isAuthenticating.value = false;
const errorMessage =
data.error ||
data.message ||
t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
useAlert(errorMessage);
};
const handleSignupCancellation = data => {
isProcessing.value = false;
authCodeReceived.value = false;
isAuthenticating.value = false;
let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
if (data.data?.current_step) {
message += ` (Step: ${data.data.current_step})`;
}
useAlert(message);
};
const handleSignupSuccess = inboxData => {
isProcessing.value = false;
isAuthenticating.value = false;
// Update the store with the new inbox data
if (inboxData && inboxData.id) {
// Add the new inbox to the store
store.commit('inboxes/ADD_INBOXES', inboxData);
useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
router.replace({
name: 'settings_inboxes_add_agents',
params: {
page: 'new',
inbox_id: inboxData.id,
},
});
} else {
// Fallback if inbox data is not properly formatted
useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUCCESS_FALLBACK'));
router.replace({
name: 'settings_inbox_list',
});
}
};
// Signup flow
const completeSignupFlow = async businessDataParam => {
if (!authCodeReceived.value || !authCode.value) {
handleSignupError({
error: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'),
});
return;
}
isProcessing.value = true;
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
);
try {
// Send both auth code and business info together (synchronous flow)
const accountId = store.getters.getCurrentAccountId;
const response = await fetch('/whatsapp/embedded_signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document
.querySelector('meta[name="csrf-token"]')
?.getAttribute('content'),
...authHeaders.value,
},
body: JSON.stringify({
account_id: accountId,
code: authCode.value,
business_id: businessDataParam.business_id,
waba_id: businessDataParam.waba_id,
phone_number_id: businessDataParam.phone_number_id,
}),
});
const responseData = await response.json();
if (response.ok) {
// Clear the stored auth code for security
authCode.value = null;
// Handle synchronous success response
handleSignupSuccess(responseData);
} else {
throw new Error(responseData.message || responseData.error);
}
} catch (error) {
handleSignupError({ error: error.message });
}
};
const isValidBusinessData = businessDataLocal => {
return (
businessDataLocal &&
(businessDataLocal.business_id || businessDataLocal.businessId) &&
(businessDataLocal.waba_id || businessDataLocal.wabaId)
);
};
// Message handling
const handleEmbeddedSignupData = async data => {
// Handle different embedded signup events per Facebook documentation
if (data.event === 'FINISH') {
// Facebook might send business data in different structures
let businessDataLocal = data.data;
// If data.data doesn't exist, try other possible structures
if (!businessDataLocal) {
businessDataLocal = data.business_data || data.details || data;
}
// Validate we have the required business information
if (isValidBusinessData(businessDataLocal)) {
// Normalize the data structure to match our backend expectations
const normalizedData = {
business_id:
businessDataLocal.business_id || businessDataLocal.businessId,
waba_id: businessDataLocal.waba_id || businessDataLocal.wabaId,
phone_number_id:
businessDataLocal.phone_number_id ||
businessDataLocal.phoneNumberId ||
businessDataLocal.phone_id,
};
// Store business data
businessData.value = normalizedData;
// Check if we already have auth code and process immediately
if (authCodeReceived.value && authCode.value) {
await completeSignupFlow(normalizedData);
} else {
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_AUTH'
);
}
} else {
handleSignupError({
error: t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.INVALID_BUSINESS_DATA'
),
});
}
} else if (data.event === 'CANCEL') {
handleSignupCancellation(data);
} else if (data.event === 'error') {
handleSignupError({
error:
data.error_message ||
t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SIGNUP_ERROR'),
error_id: data.error_id,
session_id: data.session_id,
});
}
};
const fbLoginCallback = response => {
if (response.authResponse && response.authResponse.code) {
// Authorization code received from Facebook
authCode.value = response.authResponse.code;
authCodeReceived.value = true;
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_BUSINESS_INFO'
);
// Check if we already have business data and process immediately
if (businessData.value) {
completeSignupFlow(businessData.value);
}
} else if (response.error) {
handleSignupError({ error: response.error });
} else {
isProcessing.value = false;
isAuthenticating.value = false;
useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
}
};
const handleSignupMessage = event => {
// Handle Facebook embedded signup message events
try {
const originUrl = new URL(event.origin);
const allowedHosts = [
'facebook.com',
'www.facebook.com',
'web.facebook.com',
];
if (!allowedHosts.includes(originUrl.hostname)) return;
} catch (error) {
// Invalid origin URL, reject the event
return;
}
try {
const data = JSON.parse(event.data);
if (data.type === 'WA_EMBEDDED_SIGNUP') {
handleEmbeddedSignupData(data);
}
} catch (error) {
// Handle non-JSON messages silently
}
};
// Facebook SDK
const loadFacebookSdk = () => {
if (window.FB) {
fbSdkLoaded.value = true;
return;
}
const script = document.createElement('script');
script.src = 'https://connect.facebook.net/en_US/sdk.js';
script.async = true;
script.defer = true;
script.onload = () => {
window.FB.init({
appId: window.chatwootConfig?.whatsappAppId,
status: true,
xfbml: true,
version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
});
fbSdkLoaded.value = true;
};
document.body.appendChild(script);
};
const launchEmbeddedSignup = () => {
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_PROCESSING'
);
if (!window.FB) {
loadFacebookSdk();
setTimeout(() => launchEmbeddedSignup(), 1000);
return;
}
isAuthenticating.value = true;
// Following Facebook's embedded signup documentation
window.FB.login(fbLoginCallback, {
config_id: window.chatwootConfig?.whatsappConfigurationId,
response_type: 'code',
override_default_response_type: true,
extras: {
setup: {},
featureType: '', // Leave empty for default flow
sessionInfoVersion: '3',
},
});
};
// Lifecycle
const setupMessageListener = () => {
window.addEventListener('message', handleSignupMessage);
};
const cleanupMessageListener = () => {
window.removeEventListener('message', handleSignupMessage);
};
// Initialize
const initialize = () => {
loadFacebookSdk();
setupMessageListener();
};
return {
// State
fbSdkLoaded,
isProcessing,
processingMessage,
authCodeReceived,
authCode,
businessData,
isAuthenticating,
// Computed
benefits,
showLoader,
// Methods
launchEmbeddedSignup,
initialize,
cleanupMessageListener,
};
}