bda63b6f0a
Resolved conflicts in routes.js and share.html: kept the dev tree's superset (ALLOW_LOCAL_LOGIN dev escape, avatar sync, richer login errors) which already includes the incoming production BizGaze-only behavior; took the more descriptive incoming comments. Restored 5 untracked modules (chat, calls, directory, reminders, webhooks) that were missing from disk — required by routes/signaling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 lines
2.8 KiB
JavaScript
58 lines
2.8 KiB
JavaScript
// BizGaze user-directory search (cross-tenant). The auth token is kept SERVER-SIDE only — the
|
|
// browser calls /api/directory/search and never sees the token. Configure via env in production:
|
|
// BIZGAZE_DIRECTORY_URL (base, the search term is appended url-encoded)
|
|
// BIZGAZE_DIRECTORY_TOKEN (the "stat ..." Authorization header value)
|
|
const DEFAULT_URL = 'https://app.bizgaze.com/apis/v4/bizgaze/integrations/users_chatsearch/get_usersforchatsearch/searchterm/';
|
|
const DEFAULT_TOKEN = 'stat 3cd2e190b4db448496ae316b155d2441';
|
|
|
|
function baseUrl() { return process.env.BIZGAZE_DIRECTORY_URL || DEFAULT_URL; }
|
|
function token() { return process.env.BIZGAZE_DIRECTORY_TOKEN || DEFAULT_TOKEN; }
|
|
function enabled() { return !!(baseUrl() && token()); }
|
|
|
|
// Pull a field from an object by any of several case-insensitive key names.
|
|
function field(o, names) {
|
|
const keys = Object.keys(o || {});
|
|
for (const want of names) { for (const k of keys) { if (k.toLowerCase() === want) { const v = o[k]; if (v != null && v !== '') return String(v); } } }
|
|
return '';
|
|
}
|
|
|
|
// BizGaze responses vary (raw array, or wrapped in Result/data, sometimes a JSON string). Normalize.
|
|
function toArray(data) {
|
|
let d = data;
|
|
if (typeof d === 'string') { try { d = JSON.parse(d); } catch (_) { return []; } }
|
|
if (Array.isArray(d)) return d;
|
|
if (d && typeof d === 'object') {
|
|
for (const key of ['Result', 'result', 'data', 'Data', 'records', 'Records', 'items', 'Items']) {
|
|
if (d[key] != null) { let v = d[key]; if (typeof v === 'string') { try { v = JSON.parse(v); } catch (_) {} } if (Array.isArray(v)) return v; }
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function pick(o) {
|
|
return {
|
|
id: field(o, ['userid', 'id', 'contactid', 'partyid', 'recordid']),
|
|
name: field(o, ['fullname', 'name', 'displayname', 'username', 'contactname', 'firstname']),
|
|
email: field(o, ['email', 'emailaddress', 'emailid', 'mail']),
|
|
phone: field(o, ['mobile', 'mobilenumber', 'phone', 'phonenumber', 'contactno', 'contactnumber']),
|
|
avatar: field(o, ['photourl', 'photo', 'avatar', 'imageurl', 'profilepic', 'profileimage']),
|
|
org: field(o, ['organization', 'organisation', 'company', 'tenantname', 'orgname']),
|
|
};
|
|
}
|
|
|
|
async function search(term) {
|
|
if (!enabled() || !term || term.trim().length < 2) return [];
|
|
const url = baseUrl() + encodeURIComponent(term.trim());
|
|
const ctrl = new AbortController();
|
|
const to = setTimeout(() => ctrl.abort(), 8000);
|
|
try {
|
|
const r = await fetch(url, { headers: { Authorization: token(), Accept: 'application/json' }, signal: ctrl.signal });
|
|
if (!r.ok) return [];
|
|
const data = await r.json().catch(() => null);
|
|
return toArray(data).map(pick).filter((x) => x.name || x.email || x.phone).slice(0, 25);
|
|
} catch (_) { return []; }
|
|
finally { clearTimeout(to); }
|
|
}
|
|
|
|
module.exports = { search, enabled };
|