Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased
Surveys 2.0: `Gleap.showSurvey(surveyId, options)` takes `{ format: 'card' | 'full' | 'page', fields, personalToken, container, resume }`; the old format strings (`'survey'`, `'survey_full'`, `'survey_web'`) keep working. A survey shown before the SDK is ready now opens once it is.
New survey containers: the card is a 348px corner popover with an enter animation and becomes a bottom sheet (handle, scrim, home-indicator inset) on phones; full screen covers the whole viewport and the survey draws its own background and close button (no more centred 640px card over a blurred backdrop); `page` renders inline in `container` (or `document.body`) and grows with the survey. Card and page surveys only appear once their height is known. Reduced motion is respected. Legacy surveys (not yet migrated) keep the previous card and full-screen look.
New events `survey-shown`, `survey-answered`, `survey-completed` and `survey-closed` (with `surveyId`, `version`, `responseId`, …); `outbound-sent` keeps firing as before.
New standalone `survey-embed.js` (~3 KB): `<div data-gleap-survey="ID" data-api-key="KEY" data-mode="inline|popup|tab">` loads the SDK on demand and shows the survey inline, as a popup or behind a side tab.

## 19.1.1
Fixed: the product tour and tooltip visual editor now works from the dashboard on app.gleap.ai. The SDK accepts the editor's messages from exactly `https://app.gleap.ai` and `https://app.gleap.io` (no lookalike domains), loads the editor from the dashboard that opened it, and sends its replies only to that dashboard.

Expand Down
59 changes: 58 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,64 @@ export namespace Gleap {
feedbackFlow: string,
showBackButton?: boolean
): void;
function showSurvey(surveyId: string, format?: string): void;
type SurveyFormat = "card" | "full" | "page";
interface ShowSurveyOptions {
/** card = corner popover (bottom sheet on phones), full = full screen, page = inline in `container`. Default: card. */
format?: SurveyFormat;
/** Values for the survey's hidden fields. */
fields?: Record<string, string>;
/** Signed personal-link token: attaches the response to that contact. */
personalToken?: string;
/** Element (or CSS selector) the page format renders into. Default: document.body. */
container?: HTMLElement | string;
/** Continue a started response where it was left. */
resume?: boolean;
}
/**
* Shows a survey. The legacy format strings ("survey", "survey_full", "survey_web") keep working.
*/
function showSurvey(
surveyId: string,
formatOrOptions?: "survey" | "survey_full" | "survey_web" | string | ShowSurveyOptions
): void;
/** Surveys 2.0 events (reported by the survey renderer; not fired for legacy surveys). */
interface SurveyShownEvent {
surveyId: string;
version: number;
format: SurveyFormat;
responseId: string;
/** true when a started response was continued (resume). */
resumed: boolean;
}
interface SurveyAnsweredEvent {
surveyId: string;
version: number;
responseId: string;
[key: string]: any;
}
interface SurveyCompletedEvent {
surveyId: string;
version: number;
responseId: string;
endingId: string;
/** Answers keyed by question key. */
answers: Record<string, any>;
}
interface SurveyClosedEvent {
surveyId: string;
version: number;
responseId?: string;
/** Response status when closed (e.g. "in_progress", "completed"); none when nothing was answered. */
status?: string;
}
/** The survey is visible. */
function on(event: "survey-shown", callback: (data: SurveyShownEvent) => void): void;
/** An answer was saved. */
function on(event: "survey-answered", callback: (data: SurveyAnsweredEvent) => void): void;
/** The last question was answered and an ending is shown. `outbound-sent` keeps firing as before. */
function on(event: "survey-completed", callback: (data: SurveyCompletedEvent) => void): void;
/** The survey was closed (by the user or after its ending). */
function on(event: "survey-closed", callback: (data: SurveyClosedEvent) => void): void;
function on(
event:
| "initialized"
Expand Down
84 changes: 68 additions & 16 deletions src/Gleap.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import GleapTagManager from './GleapTagManager';
import GleapThemeManager from './GleapThemeManager';
import GleapTranslationManager from './GleapTranslationManager';
import { injectStyledCSS } from './UI';
import { normalizeSurveyOptions } from './GleapSurveyOptions';

if (
typeof window !== 'undefined' &&
Expand Down Expand Up @@ -339,7 +340,8 @@ class Gleap {
const surveyFlow = urlParams.get('gleap_survey');
const surveyFlowFormat = urlParams.get('gleap_survey_format');
if (surveyFlow && surveyFlow.length > 0) {
Gleap.showSurvey(surveyFlow, surveyFlowFormat === 'survey_full' ? 'survey_full' : 'survey');
const fullScreen = surveyFlowFormat === 'survey_full' || surveyFlowFormat === 'full';
Gleap.showSurvey(surveyFlow, fullScreen ? 'survey_full' : 'survey');
}
const tourId = urlParams.get('gleap_tour');
if (tourId && tourId.length > 0) {
Expand Down Expand Up @@ -991,16 +993,31 @@ class Gleap {
}

/**
* Shows a survey manually.
* @param {*} actionType
* @param {*} format
*/
static showSurvey(actionType, format = 'survey') {
* Shows a survey.
*
* The second argument is either a legacy format string ('survey', 'survey_full', 'survey_web')
* or an options object: { format?: 'card' | 'full' | 'page', fields?: Record<string, string>,
* personalToken?: string, container?: HTMLElement | string, resume?: boolean }.
* @param {string} surveyId
* @param {string|object} formatOrOptions
*/
static showSurvey(surveyId, formatOrOptions = 'survey') {
// Called before the session is ready (right after initialize): show it once it is.
const sessionInstance = GleapSession.getInstance();
if (!sessionInstance.ready) {
sessionInstance.setOnSessionReady(() => {
Gleap.showSurvey(surveyId, formatOrOptions);
});
return;
}

const options = normalizeSurveyOptions(formatOrOptions);
Gleap.startFeedbackFlowWithOptions(
actionType,
surveyId,
{
hideBackButton: true,
format,
format: options.appMode,
survey: options,
},
true
);
Expand Down Expand Up @@ -1028,7 +1045,7 @@ class Gleap {
* Starts the bug reporting flow.
*/
static startFeedbackFlowWithOptions(feedbackFlow, options = {}, isSurvey = false) {
const { autostartDrawing, hideBackButton, format } = options;
const { autostartDrawing, hideBackButton, format, survey } = options;
const sessionInstance = GleapSession.getInstance();
if (!sessionInstance.ready) {
return;
Expand All @@ -1045,16 +1062,42 @@ class Gleap {
action = 'start-survey';
}

GleapFrameManager.getInstance().setAppMode(isSurvey ? format : 'widget');
const surveyOptions = isSurvey ? survey || normalizeSurveyOptions(format) : null;
const appMode = surveyOptions ? surveyOptions.appMode : 'widget';

// The page format renders inline: the messenger frame lives in the given container.
// Every other survey format (and the widget) lives in document.body.
GleapFrameManager.getInstance().setFrameHost(
surveyOptions && surveyOptions.format === 'page' ? surveyOptions.container : null
);
GleapFrameManager.getInstance().setAppMode(appMode);

const messageData = {
flow: feedbackFlow,
hideBackButton: hideBackButton,
// `format` stays the messenger app mode ('survey' | 'survey_full' | 'survey_web' | 'survey_page').
format: surveyOptions ? appMode : format,
};
if (surveyOptions) {
messageData.surveyFormat = surveyOptions.format;
messageData.fields = surveyOptions.fields;
if (surveyOptions.personalToken) {
messageData.personalToken = surveyOptions.personalToken;
}
messageData.resume = surveyOptions.resume;
if (surveyOptions.outboundAction) {
messageData.outboundAction = surveyOptions.outboundAction;
}
if (surveyOptions.resumeData) {
messageData.resumeData = surveyOptions.resumeData;
}
messageData.safeArea = GleapFrameManager.getInstance().getSafeAreaInsets();
}

GleapFrameManager.getInstance().sendMessage(
{
name: action,
data: {
flow: feedbackFlow,
hideBackButton: hideBackButton,
format,
},
data: messageData,
},
true
);
Expand Down Expand Up @@ -1544,7 +1587,16 @@ class Gleap {
} else if (action.actionType === 'tour') {
Gleap.startProductTourWithConfig(action.outbound, action.data, false);
} else {
Gleap.showSurvey(action.actionType, action.format);
// An inline survey page owns the messenger frame: never swap it for a triggered survey.
if (GleapFrameManager.getInstance().appMode === 'survey_page') {
continue;
}
Gleap.showSurvey(action.actionType, {
format: action.format,
resume: action.resume === true,
resumeData: action.resume === true ? action.data : undefined,
outboundAction: action._id || action.id,
});
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/GleapFeedbackButtonManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,13 @@ export default class GleapFeedbackButtonManager {
this.feedbackButton.classList.add('bb-feedback-button--bottomleft');
}

if (GleapFrameManager.getInstance().isOpened()) {
const appMode = GleapFrameManager.getInstance().appMode;

// An inline survey page doesn't open the messenger: the launcher keeps its state.
if (GleapFrameManager.getInstance().isOpened() && appMode !== 'survey_page') {
this.feedbackButton.classList.add('bb-feedback-button--open');
}

const appMode = GleapFrameManager.getInstance().appMode;
if (appMode === 'survey' || appMode === 'survey_full' || appMode === 'survey_web') {
this.feedbackButton.classList.add('bb-feedback-button--survey');
}
Expand Down
Loading
Loading