Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/Gleap.js
Original file line number Diff line number Diff line change
Expand Up @@ -1011,16 +1011,20 @@ class Gleap {
return;
}

const options = normalizeSurveyOptions(formatOrOptions);
Gleap.startFeedbackFlowWithOptions(
surveyId,
{
hideBackButton: true,
format: options.appMode,
survey: options,
},
true
);
// The messenger reads the contact once, when the survey starts, to skip the contact questions
// it already knows: wait for an identify / updateContact still in flight (capped).
sessionInstance.onContactSettled(() => {
const options = normalizeSurveyOptions(formatOrOptions);
Gleap.startFeedbackFlowWithOptions(
surveyId,
{
hideBackButton: true,
format: options.appMode,
survey: options,
},
true
);
});
}

/**
Expand Down
31 changes: 26 additions & 5 deletions src/GleapFrameManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export default class GleapFrameManager {
// Card and page surveys stay invisible until the messenger reports their height (no tall blank flash).
surveyAwaitingHeight = false;
surveyRevealTimeout = null;
// A freshly injected frame opens shortly after its first ping, unless it was closed meanwhile.
openAfterPingTimeout = null;
// The messenger fell back to a legacy (pre Surveys 2.0) survey: use the old survey chrome.
surveyLegacy = false;
// Surveys 2.0 corners reported by the messenger (survey-theme): 'sharp' | 'pill', null = rounded.
Expand Down Expand Up @@ -154,8 +156,12 @@ export default class GleapFrameManager {
this.surveyDark = null;
this.updateFrameStyle();

// Wait for the survey's height before showing a card/page survey in a frame that has none yet.
this.surveyAwaitingHeight = this.isAutoHeightSurvey() && (!this.comReady || !this.frameHeight);
// A card/page survey waits for its own height before it shows, also in a frame that already
// measured an earlier survey (the messenger reports none for a survey with nothing to ask).
if (this.isAutoHeightSurvey()) {
this.frameHeight = 0;
}
this.surveyAwaitingHeight = this.isAutoHeightSurvey();
this.applyInnerSize();

// Lay the hidden frame out (invisibly) so the messenger can measure the survey right away.
Expand Down Expand Up @@ -539,6 +545,8 @@ export default class GleapFrameManager {
clearTimeout(this.surveyRevealTimeout);
this.surveyRevealTimeout = null;
}
clearTimeout(this.openAfterPingTimeout);
this.openAfterPingTimeout = null;
this.gleapFrameContainer = null;
this.gleapFrame = null;
this.captureHidden = false;
Expand Down Expand Up @@ -950,6 +958,15 @@ export default class GleapFrameManager {
return;
}

// A survey the messenger closes before it was shown (nothing to ask): it must not open after
// all, and there is nothing to animate and no close event without an open.
const wasOpened = this.widgetOpened;
const wasVisible = this.isOpened();
if (this.openAfterPingTimeout) {
clearTimeout(this.openAfterPingTimeout);
this.openAfterPingTimeout = null;
}

this.hideMarkerManager();
this.setCaptureEditorOpen(false);
if (this.gleapFrameContainer) {
Expand All @@ -966,7 +983,7 @@ export default class GleapFrameManager {
this.surveyRevealTimeout = null;
}

if (container.classList.contains('gleap-frame-container--measuring')) {
if (!wasOpened || container.classList.contains('gleap-frame-container--measuring')) {
// Never shown: nothing to animate.
container.classList.remove('gleap-frame-container--measuring');
container.classList.add('gleap-frame-container--hidden');
Expand All @@ -989,7 +1006,9 @@ export default class GleapFrameManager {
this.widgetOpened = false;
this.updateWidgetStatus();
GleapFeedbackButtonManager.getInstance().updateFeedbackButtonState();
GleapEventManager.notifyEvent('close');
if (wasVisible) {
GleapEventManager.notifyEvent('close');
}
GleapNotificationManager.getInstance().reloadNotificationsFromCache();

this.unregisterEscListener();
Expand Down Expand Up @@ -1073,7 +1092,9 @@ export default class GleapFrameManager {
this.sendSessionUpdate();
this.sendCaptureCapabilities();
this.workThroughQueue();
setTimeout(() => {
clearTimeout(this.openAfterPingTimeout);
this.openAfterPingTimeout = setTimeout(() => {
this.openAfterPingTimeout = null;
this.runWidgetShouldOpenCallback();
}, 300);
}
Expand Down
59 changes: 55 additions & 4 deletions src/GleapSession.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export default class GleapSession {
// never re-identified from stale state.
lastIdentify = null;
identifyInFlight = false;
// identify / updateContact requests still waiting for the server. A survey
// started meanwhile would get the previous contact (Surveys 2.0 skips the
// contact questions an identified contact already answered), so showSurvey
// waits for them (onContactSettled).
pendingContactUpdates = 0;
contactSettledListeners = [];

// GleapSession singleton
static instance;
Expand Down Expand Up @@ -147,6 +153,51 @@ export default class GleapSession {
}
};

/**
* Counts an identify / updateContact request until it settles. The session
* it returns is applied (validateSession, session-update to the messenger)
* before the listeners run.
*/
trackContactUpdate = (request) => {
this.pendingContactUpdates++;
const settle = () => {
this.pendingContactUpdates = Math.max(0, this.pendingContactUpdates - 1);
if (this.pendingContactUpdates > 0) {
return;
}
const listeners = this.contactSettledListeners;
this.contactSettledListeners = [];
for (let i = 0; i < listeners.length; i++) {
listeners[i]();
}
};
request.then(settle, settle);
return request;
};

/**
* Runs `callback` once no identify / updateContact request is in flight, or
* after `timeoutMs` at the latest.
*/
onContactSettled = (callback, timeoutMs = 5000) => {
if (this.pendingContactUpdates === 0) {
callback();
return;
}
let done = false;
let timer = null;
const run = () => {
if (done) {
return;
}
done = true;
clearTimeout(timer);
callback();
};
timer = setTimeout(run, timeoutMs);
this.contactSettledListeners.push(run);
};

injectSession = (http) => {
if (http && this.session) {
http.setRequestHeader('Api-Token', this.sdkKey);
Expand Down Expand Up @@ -477,7 +528,7 @@ export default class GleapSession {
}

const self = this;
return new Promise((resolve, reject) => {
return this.trackContactUpdate(new Promise((resolve, reject) => {
// Wait for gleap session to be ready.
this.setOnSessionReady(function () {
if (!self.session.gleapId || !self.session.gleapHash) {
Expand Down Expand Up @@ -526,7 +577,7 @@ export default class GleapSession {
})
);
});
});
}));
};

identifySession = (userId, userData, userHash, refreshFileSession = false) => {
Expand All @@ -541,7 +592,7 @@ export default class GleapSession {
}

const self = this;
return new Promise((resolve, reject) => {
return this.trackContactUpdate(new Promise((resolve, reject) => {
// Wait for gleap session to be ready.
this.setOnSessionReady(function () {
if (!self.session.gleapId || !self.session.gleapHash) {
Expand Down Expand Up @@ -603,7 +654,7 @@ export default class GleapSession {
})
);
});
});
}));
};

startProductTourConfig = (tourId) => {
Expand Down
Loading