Skip to main content
Glama
surveys-utils.jsx27.8 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; import { cloneElement, createContext } from 'preact'; import { SurveySchedule, SurveyType, } from '../../posthog-surveys-types'; import { document as _document, window as _window } from '../../utils/globals'; import { createLogger } from '../../utils/logger'; import { prepareStylesheet } from '../utils/stylesheet-loader'; // We cast the types here which is dangerous but protected by the top level generateSurveys call var window = _window; var document = _document; var SurveySeenPrefix = 'seenSurvey_'; var logger = createLogger('[Surveys]'); export var SURVEY_DEFAULT_Z_INDEX = 2147483647; export function getFontFamily(fontFamily) { if (fontFamily === 'inherit') { return 'inherit'; } var defaultFontStack = 'BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"'; return fontFamily ? "".concat(fontFamily, ", ").concat(defaultFontStack) : "-apple-system, ".concat(defaultFontStack); } export function getSurveyResponseKey(questionId) { return "$survey_response_".concat(questionId); } export var style = function (appearance) { var positions = { left: 'left: 30px;', right: 'right: 30px;', center: "\n left: 50%;\n transform: translateX(-50%);\n ", }; return "\n .survey-form, .thank-you-message {\n position: fixed;\n margin: 0px;\n bottom: 0px;\n color: black;\n font-weight: normal;\n font-family: ".concat(getFontFamily(appearance === null || appearance === void 0 ? void 0 : appearance.fontFamily), ";\n text-align: left;\n max-width: ").concat(parseInt((appearance === null || appearance === void 0 ? void 0 : appearance.maxWidth) || '300'), "px;\n width: 100%;\n z-index: ").concat(parseInt((appearance === null || appearance === void 0 ? void 0 : appearance.zIndex) || SURVEY_DEFAULT_Z_INDEX.toString()), ";\n border: 1.5px solid ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.borderColor) || '#c9c6c6', ";\n border-bottom: 0px;\n ").concat(positions[(appearance === null || appearance === void 0 ? void 0 : appearance.position) || 'right'] || 'right: 30px;', "\n flex-direction: column;\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n border-top-left-radius: 10px;\n border-top-right-radius: 10px;\n box-shadow: -6px 0 16px -8px rgb(0 0 0 / 8%), -9px 0 28px 0 rgb(0 0 0 / 5%), -12px 0 48px 16px rgb(0 0 0 / 3%);\n }\n\n .survey-box, .thank-you-message-container {\n padding: 20px 25px 10px;\n display: flex;\n flex-direction: column;\n border-radius: 10px;\n }\n\n .thank-you-message {\n text-align: center;\n }\n\n .form-submit[disabled] {\n opacity: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.disabledButtonOpacity) || '0.6', ";\n filter: grayscale(50%);\n cursor: not-allowed;\n }\n .survey-form textarea {\n color: #2d2d2d;\n font-size: 14px;\n font-family: ").concat(getFontFamily(appearance === null || appearance === void 0 ? void 0 : appearance.fontFamily), ";\n background: white;\n color: black;\n outline: none;\n padding-left: 10px;\n padding-right: 10px;\n padding-top: 10px;\n border-radius: 6px;\n border-color: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.borderColor) || '#c9c6c6', ";\n margin-top: 14px;\n width: 100%;\n box-sizing: border-box;\n }\n .survey-box:has(.survey-question:empty):not(:has(.survey-question-description)) textarea {\n margin-top: 0;\n }\n .form-submit {\n box-sizing: border-box;\n margin: 0;\n font-family: inherit;\n overflow: visible;\n text-transform: none;\n position: relative;\n display: inline-block;\n font-weight: 700;\n white-space: nowrap;\n text-align: center;\n border: 1.5px solid transparent;\n cursor: pointer;\n user-select: none;\n touch-action: manipulation;\n padding: 12px;\n font-size: 14px;\n border-radius: 6px;\n outline: 0;\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.submitButtonColor) || 'black', " !important;\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);\n box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);\n width: 100%;\n }\n .form-cancel {\n display: flex;\n float: right;\n border: none;\n background: none;\n cursor: pointer;\n }\n .cancel-btn-wrapper {\n position: absolute;\n width: 35px;\n height: 35px;\n border-radius: 100%;\n top: 0;\n right: 0;\n transform: translate(50%, -50%);\n background: white;\n border: 1.5px solid ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.borderColor) || '#c9c6c6', ";\n display: flex;\n justify-content: center;\n align-items: center;\n }\n .bolded { font-weight: 600; }\n .buttons {\n display: flex;\n justify-content: center;\n }\n .footer-branding {\n font-size: 11px;\n margin-top: 10px;\n text-align: center;\n display: flex;\n justify-content: center;\n gap: 4px;\n align-items: center;\n font-weight: 500;\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n text-decoration: none;\n backgroundColor: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n color: ").concat(getContrastingTextColor((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded'), ";\n }\n .survey-question {\n font-weight: 500;\n font-size: 14px;\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n }\n .question-textarea-wrapper {\n display: flex;\n flex-direction: column;\n }\n .survey-question-description {\n font-size: 13px;\n padding-top: 5px;\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n }\n .ratings-number {\n font-size: 16px;\n font-weight: 600;\n padding: 8px 0px;\n border: none;\n }\n .ratings-number:hover {\n cursor: pointer;\n }\n .rating-options {\n margin-top: 14px;\n }\n .rating-options-number {\n display: grid;\n border-radius: 6px;\n overflow: hidden;\n border: 1.5px solid ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.borderColor) || '#c9c6c6', ";\n }\n .rating-options-number > .ratings-number {\n border-right: 1px solid ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.borderColor) || '#c9c6c6', ";\n }\n .rating-options-number > .ratings-number:last-of-type {\n border-right: 0px;\n }\n .rating-options-number .rating-active {\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.ratingButtonActiveColor) || 'black', ";\n }\n .rating-options-emoji {\n display: flex;\n justify-content: space-between;\n }\n .ratings-emoji {\n font-size: 16px;\n background-color: transparent;\n border: none;\n padding: 0px;\n }\n .ratings-emoji:hover {\n cursor: pointer;\n }\n .ratings-emoji.rating-active svg {\n fill: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.ratingButtonActiveColor) || 'black', ";\n }\n .emoji-svg {\n fill: '#939393';\n }\n .rating-text {\n display: flex;\n flex-direction: row;\n font-size: 11px;\n justify-content: space-between;\n margin-top: 6px;\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n opacity: .60;\n }\n .limit-height {\n max-height: 300px;\n overflow: auto;\n scrollbar-width: thin;\n scrollbar-color: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.borderColor) || '#c9c6c6', " ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n }\n .multiple-choice-options {\n margin-top: 13px;\n font-size: 14px;\n }\n .survey-box:has(.survey-question:empty):not(:has(.survey-question-description)) .multiple-choice-options {\n margin-top: 0;\n }\n .multiple-choice-options .choice-option {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 13px;\n cursor: pointer;\n margin-bottom: 5px;\n position: relative;\n }\n .multiple-choice-options > .choice-option:last-of-type {\n margin-bottom: 0px;\n }\n .multiple-choice-options input {\n cursor: pointer;\n position: absolute;\n opacity: 0;\n }\n .choice-check {\n position: absolute;\n right: 10px;\n background: white;\n }\n .choice-check svg {\n display: none;\n }\n .multiple-choice-options .choice-option:hover .choice-check svg {\n display: inline-block;\n opacity: .25;\n }\n .multiple-choice-options input:checked + label + .choice-check svg {\n display: inline-block;\n opacity: 100% !important;\n }\n .multiple-choice-options input:checked + label {\n font-weight: bold;\n border: 1.5px solid rgba(0,0,0);\n }\n .multiple-choice-options input:checked + label input {\n font-weight: bold;\n }\n .multiple-choice-options label {\n width: 100%;\n cursor: pointer;\n padding: 10px;\n border: 1.5px solid rgba(0,0,0,.25);\n border-radius: 4px;\n background: white;\n }\n .multiple-choice-options .choice-option-open label {\n padding-right: 30px;\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n max-width: 100%;\n }\n .multiple-choice-options .choice-option-open label span {\n width: 100%;\n }\n .multiple-choice-options .choice-option-open input:disabled + label {\n opacity: 0.6;\n }\n .multiple-choice-options .choice-option-open label input {\n position: relative;\n opacity: 1;\n flex-grow: 1;\n border: 0;\n outline: 0;\n }\n .thank-you-message-body {\n margin-top: 6px;\n font-size: 14px;\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n }\n .thank-you-message-header {\n margin: 10px 0px 0px;\n background: ").concat((appearance === null || appearance === void 0 ? void 0 : appearance.backgroundColor) || '#eeeded', ";\n }\n .thank-you-message-container .form-submit {\n margin-top: 20px;\n margin-bottom: 10px;\n }\n .thank-you-message-countdown {\n margin-left: 6px;\n }\n .bottom-section {\n margin-top: 14px;\n }\n "); }; function nameToHex(name) { return { aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', black: '#000000', blanchedalmond: '#ffebcd', blue: '#0000ff', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cyan: '#00ffff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgreen: '#006400', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', fuchsia: '#ff00ff', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', gray: '#808080', green: '#008000', greenyellow: '#adff2f', honeydew: '#f0fff0', hotpink: '#ff69b4', 'indianred ': '#cd5c5c', indigo: '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', lavender: '#e6e6fa', lavenderblush: '#fff0f5', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrodyellow: '#fafad2', lightgrey: '#d3d3d3', lightgreen: '#90ee90', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', lime: '#00ff00', limegreen: '#32cd32', linen: '#faf0e6', magenta: '#ff00ff', maroon: '#800000', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370d8', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', navy: '#000080', oldlace: '#fdf5e6', olive: '#808000', olivedrab: '#6b8e23', orange: '#ffa500', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#d87093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', purple: '#800080', red: '#ff0000', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', silver: '#c0c0c0', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', teal: '#008080', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', white: '#ffffff', whitesmoke: '#f5f5f5', yellow: '#ffff00', yellowgreen: '#9acd32', }[name.toLowerCase()]; } function hex2rgb(c) { if (c[0] === '#') { var hexColor = c.replace(/^#/, ''); var r = parseInt(hexColor.slice(0, 2), 16); var g = parseInt(hexColor.slice(2, 4), 16); var b = parseInt(hexColor.slice(4, 6), 16); return 'rgb(' + r + ',' + g + ',' + b + ')'; } return 'rgb(255, 255, 255)'; } export function getContrastingTextColor(color) { if (color === void 0) { color = defaultBackgroundColor; } var rgb; if (color[0] === '#') { rgb = hex2rgb(color); } if (color.startsWith('rgb')) { rgb = color; } // otherwise it's a color name var nameColorToHex = nameToHex(color); if (nameColorToHex) { rgb = hex2rgb(nameColorToHex); } if (!rgb) { return 'black'; } var colorMatch = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/); if (colorMatch) { var r = parseInt(colorMatch[1]); var g = parseInt(colorMatch[2]); var b = parseInt(colorMatch[3]); var hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)); return hsp > 127.5 ? 'black' : 'white'; } return 'black'; } export function getTextColor(el) { var backgroundColor = window.getComputedStyle(el).backgroundColor; if (backgroundColor === 'rgba(0, 0, 0, 0)') { return 'black'; } var colorMatch = backgroundColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/); if (!colorMatch) return 'black'; var r = parseInt(colorMatch[1]); var g = parseInt(colorMatch[2]); var b = parseInt(colorMatch[3]); var hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)); return hsp > 127.5 ? 'black' : 'white'; } export var defaultSurveyAppearance = { backgroundColor: '#eeeded', submitButtonColor: 'black', submitButtonTextColor: 'white', ratingButtonColor: 'white', ratingButtonActiveColor: 'black', borderColor: '#c9c6c6', placeholder: 'Start typing...', whiteLabel: false, displayThankYouMessage: true, thankYouMessageHeader: 'Thank you for your feedback!', position: 'right', }; export var defaultBackgroundColor = '#eeeded'; export var createShadow = function (styleSheet, surveyId, element, posthog) { var div = document.createElement('div'); div.className = "PostHogSurvey".concat(surveyId); var shadow = div.attachShadow({ mode: 'open' }); if (styleSheet) { var styleElement = prepareStylesheet(document, styleSheet, posthog); if (styleElement) { shadow.appendChild(styleElement); } } ; (element ? element : document.body).appendChild(div); return shadow; }; export var sendSurveyEvent = function (responses, survey, posthog) { var _a; var _b; if (responses === void 0) { responses = {}; } if (!posthog) { logger.error('[survey sent] event not captured, PostHog instance not found.'); return; } localStorage.setItem(getSurveySeenKey(survey), 'true'); posthog.capture('survey sent', __assign(__assign({ $survey_name: survey.name, $survey_id: survey.id, $survey_iteration: survey.current_iteration, $survey_iteration_start_date: survey.current_iteration_start_date, $survey_questions: survey.questions.map(function (question, index) { return ({ id: question.id, question: question.question, index: index, }); }), sessionRecordingUrl: (_b = posthog.get_session_replay_url) === null || _b === void 0 ? void 0 : _b.call(posthog) }, responses), { $set: (_a = {}, _a[getSurveyInteractionProperty(survey, 'responded')] = true, _a) })); window.dispatchEvent(new Event('PHSurveySent')); }; export var dismissedSurveyEvent = function (survey, posthog, readOnly) { var _a; var _b; // TODO: state management and unit tests for this would be nice if (!posthog) { logger.error('[survey dismissed] event not captured, PostHog instance not found.'); return; } if (readOnly) { return; } posthog.capture('survey dismissed', { $survey_name: survey.name, $survey_id: survey.id, $survey_iteration: survey.current_iteration, $survey_iteration_start_date: survey.current_iteration_start_date, sessionRecordingUrl: (_b = posthog.get_session_replay_url) === null || _b === void 0 ? void 0 : _b.call(posthog), $set: (_a = {}, _a[getSurveyInteractionProperty(survey, 'dismissed')] = true, _a), }); localStorage.setItem(getSurveySeenKey(survey), 'true'); window.dispatchEvent(new Event('PHSurveyClosed')); }; // Use the Fisher-yates algorithm to shuffle this array // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle export var shuffle = function (array) { return array .map(function (a) { return ({ sort: Math.floor(Math.random() * 10), value: a }); }) .sort(function (a, b) { return a.sort - b.sort; }) .map(function (a) { return a.value; }); }; var reverseIfUnshuffled = function (unshuffled, shuffled) { if (unshuffled.length === shuffled.length && unshuffled.every(function (val, index) { return val === shuffled[index]; })) { return shuffled.reverse(); } return shuffled; }; export var getDisplayOrderChoices = function (question) { if (!question.shuffleOptions) { return question.choices; } var displayOrderChoices = question.choices; var openEndedChoice = ''; if (question.hasOpenChoice) { // if the question has an open-ended choice, its always the last element in the choices array. openEndedChoice = displayOrderChoices.pop(); } var shuffledOptions = reverseIfUnshuffled(displayOrderChoices, shuffle(displayOrderChoices)); if (question.hasOpenChoice) { question.choices.push(openEndedChoice); shuffledOptions.push(openEndedChoice); } return shuffledOptions; }; export var getDisplayOrderQuestions = function (survey) { if (!survey.appearance || !survey.appearance.shuffleQuestions) { return survey.questions; } return reverseIfUnshuffled(survey.questions, shuffle(survey.questions)); }; export var hasEvents = function (survey) { var _a, _b, _c, _d, _e, _f; return ((_c = (_b = (_a = survey.conditions) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.values) === null || _c === void 0 ? void 0 : _c.length) != undefined && ((_f = (_e = (_d = survey.conditions) === null || _d === void 0 ? void 0 : _d.events) === null || _e === void 0 ? void 0 : _e.values) === null || _f === void 0 ? void 0 : _f.length) > 0; }; export var canActivateRepeatedly = function (survey) { var _a, _b; if (survey.schedule === SurveySchedule.Always && survey.type === SurveyType.Widget) { return true; } return !!(((_b = (_a = survey.conditions) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.repeatedActivation) && hasEvents(survey)); }; /** * getSurveySeen checks local storage for the surveySeen Key a * and overrides this value if the survey can be repeatedly activated by its events. * @param survey */ export var getSurveySeen = function (survey) { var surveySeen = localStorage.getItem(getSurveySeenKey(survey)); if (surveySeen) { // if a survey has already been seen, // we will override it with the event repeated activation value. return !canActivateRepeatedly(survey); } return false; }; export var getSurveySeenKey = function (survey) { var surveySeenKey = "".concat(SurveySeenPrefix).concat(survey.id); if (survey.current_iteration && survey.current_iteration > 0) { surveySeenKey = "".concat(SurveySeenPrefix).concat(survey.id, "_").concat(survey.current_iteration); } return surveySeenKey; }; export var getSurveySeenStorageKeys = function () { var surveyKeys = []; for (var i = 0; i < localStorage.length; i++) { var key = localStorage.key(i); if (key === null || key === void 0 ? void 0 : key.startsWith(SurveySeenPrefix)) { surveyKeys.push(key); } } return surveyKeys; }; var getSurveyInteractionProperty = function (survey, action) { var surveyProperty = "$survey_".concat(action, "/").concat(survey.id); if (survey.current_iteration && survey.current_iteration > 0) { surveyProperty = "$survey_".concat(action, "/").concat(survey.id, "/").concat(survey.current_iteration); } return surveyProperty; }; export var hasWaitPeriodPassed = function (lastSeenSurveyDate, waitPeriodInDays) { if (!waitPeriodInDays || !lastSeenSurveyDate) { return true; } var today = new Date(); var diff = Math.abs(today.getTime() - new Date(lastSeenSurveyDate).getTime()); var diffDaysFromToday = Math.ceil(diff / (1000 * 3600 * 24)); return diffDaysFromToday > waitPeriodInDays; }; export var SurveyContext = createContext({ isPreviewMode: false, previewPageIndex: 0, onPopupSurveyDismissed: function () { }, isPopup: true, onPreviewSubmit: function () { }, onPopupSurveySent: function () { }, }); export var renderChildrenAsTextOrHtml = function (_a) { var component = _a.component, children = _a.children, renderAsHtml = _a.renderAsHtml, style = _a.style; return renderAsHtml ? cloneElement(component, { dangerouslySetInnerHTML: { __html: children }, style: style, }) : cloneElement(component, { children: children, style: style, }); }; //# sourceMappingURL=surveys-utils.jsx.map

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sadiuysal/mem0-mcp-server-ts'

If you have feedback or need assistance with the MCP directory API, please join our Discord server