113 lines
4.1 KiB
JavaScript
113 lines
4.1 KiB
JavaScript
/**
|
|
* Shared config — same values as the dashboard frontend's
|
|
* VITE_API_BASE_URL / VITE_API_KEY (CLAUDE.md §9: every route requires
|
|
* X-API-Key). This is a single app-wide key baked into the published
|
|
* script, not a per-PM secret — same tradeoff as embedding it in the
|
|
* frontend bundle. Real per-PM authorization is the email check inside
|
|
* validateLicence() below, not this header.
|
|
*
|
|
* TEMPORARY — pointed at a local dev cloudflared quick tunnel for
|
|
* testing the pull flow end-to-end before a real production domain
|
|
* exists. Quick tunnels are not stable: the hostname changes every
|
|
* time cloudflared restarts, and Cloudflare gives no uptime guarantee
|
|
* on the free, account-less tier. Replace both values with the real
|
|
* production API base URL + API_SECRET_KEY before publishing the
|
|
* add-on for real PM use.
|
|
*/
|
|
const BETTERSIGHT_API_BASE_URL = 'https://api.bettersight.io';
|
|
const BETTERSIGHT_API_KEY = 'REPLACE_WITH_PRODUCTION_API_KEY';
|
|
|
|
const CACHE_KEY_VALID = 'BETTERSIGHT_VALID';
|
|
const CACHE_KEY_TIER = 'BETTERSIGHT_TIER';
|
|
const CACHE_DURATION_SECONDS = 21600; // 6 hours
|
|
|
|
/**
|
|
* Flips a single onboarding_checklist flag to true on the PM's tenant,
|
|
* exactly once per install (tracked locally via UserProperties so a
|
|
* repeat success — e.g. every pull, or every cache expiry — doesn't
|
|
* re-send the same PATCH). The Apps Script has no PocketBase JWT, so
|
|
* it authenticates this call by email only, same as validateLicence()
|
|
* — the backend's PATCH /account/onboarding accepts either.
|
|
*
|
|
* A failure here is a side-effect failure only: it never blocks the
|
|
* primary action (validating a licence, pulling results) that
|
|
* triggered it.
|
|
*/
|
|
function markOnboardingStepOnce_(flagName) {
|
|
const properties = PropertiesService.getUserProperties();
|
|
const sentKey = 'ONBOARDING_SENT_' + flagName;
|
|
if (properties.getProperty(sentKey) === 'true') {
|
|
return;
|
|
}
|
|
|
|
const email = Session.getActiveUser().getEmail();
|
|
if (!email) return;
|
|
|
|
try {
|
|
const payload = { email: email };
|
|
payload[flagName] = true;
|
|
UrlFetchApp.fetch(`${BETTERSIGHT_API_BASE_URL}/account/onboarding`, {
|
|
method: 'patch',
|
|
contentType: 'application/json',
|
|
headers: { 'X-API-Key': BETTERSIGHT_API_KEY },
|
|
payload: JSON.stringify(payload),
|
|
muteHttpExceptions: true
|
|
});
|
|
properties.setProperty(sentKey, 'true');
|
|
} catch (e) {
|
|
Logger.log('markOnboardingStepOnce_ failed for ' + flagName + ': ' + e.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates the current user's licence against the Bettersight API.
|
|
* Standalone script — validates by email only, no sheet binding of
|
|
* any kind (CLAUDE.md §15).
|
|
*
|
|
* Data flow:
|
|
* check 6-hour cache → cache hit: return true immediately →
|
|
* cache miss: Session.getActiveUser().getEmail() →
|
|
* POST {BETTERSIGHT_API_BASE_URL}/validate-email →
|
|
* valid: cache result + tier for 6 hours, return true →
|
|
* invalid: show a UI alert with the rejection reason, return false
|
|
*/
|
|
function validateLicence() {
|
|
const cache = CacheService.getUserCache();
|
|
const cachedValid = cache.get(CACHE_KEY_VALID);
|
|
if (cachedValid === 'true') {
|
|
return true;
|
|
}
|
|
|
|
const email = Session.getActiveUser().getEmail();
|
|
if (!email) {
|
|
SpreadsheetApp.getUi().alert('Could not determine your Google account email. Please try again.');
|
|
return false;
|
|
}
|
|
|
|
let response;
|
|
try {
|
|
response = UrlFetchApp.fetch(`${BETTERSIGHT_API_BASE_URL}/validate-email`, {
|
|
method: 'post',
|
|
contentType: 'application/json',
|
|
headers: { 'X-API-Key': BETTERSIGHT_API_KEY },
|
|
payload: JSON.stringify({ email: email }),
|
|
muteHttpExceptions: true
|
|
});
|
|
} catch (e) {
|
|
SpreadsheetApp.getUi().alert('Could not reach Bettersight — check your connection and try again.');
|
|
return false;
|
|
}
|
|
|
|
const body = JSON.parse(response.getContentText());
|
|
|
|
if (body.valid) {
|
|
cache.put(CACHE_KEY_VALID, 'true', CACHE_DURATION_SECONDS);
|
|
cache.put(CACHE_KEY_TIER, body.tier || '', CACHE_DURATION_SECONDS);
|
|
markOnboardingStepOnce_('sidebar_installed');
|
|
return true;
|
|
}
|
|
|
|
SpreadsheetApp.getUi().alert(`Bettersight access denied: ${body.reason || 'Unknown reason'}`);
|
|
return false;
|
|
}
|