UNPKG

@open-condo/miniapp-utils

Version:

A set of helper functions / components / hooks used to build new condo apps fast

1 lines 51.7 kB
{"version":3,"sources":["../src/helpers/analytics/instance.ts","../src/helpers/analytics/middlewares/grouping.ts","../src/helpers/analytics/middlewares/identity.ts","../src/helpers/apollo.ts","../src/helpers/proxying/utils.ts","../src/helpers/proxying/proxy.ts","../src/helpers/sender.ts","../src/helpers/uuid.ts","../src/helpers/collections.ts","../src/helpers/cookies.ts","../src/helpers/environment.ts","../src/helpers/posthog.ts","../src/hooks/useEffectOnce.ts","../src/hooks/useIntersectionObserver.ts","../src/hooks/usePrevious.ts"],"sourcesContent":["import { Analytics as DefaultAnalytics } from 'analytics'\n\nimport { GroupingMiddlewarePlugin, IdentityMiddlewarePlugin } from './middlewares'\n\nimport type { AnyPayload, AnalyticsConfig, AnalyticsInstanceWithGroups, PageData } from './types'\n\n/**\n * Type-safe wrapper on top of \"[analytics](https://www.npmjs.com/package/analytics)\" npm package\n * with support of group method (Posthog inspired) to group users into cohorts\n *\n * @example Init analytics in your app with type-guards\n * import { Analytics } from '@open-condo/miniapp-utils/helpers/analytics'\n * import { isDebug } from '@open-condo/miniapp-utils/helpers/environment'\n *\n * type MyAppEvents = {\n * 'order_create': { orderId: string, itemIds: Array<string> }\n * 'user_register': { userId: string, utmSource?: string }\n * }\n *\n * type MyUserData = {\n * 'name'?: string\n * 'age'?: number\n * }\n *\n * type MyAppGroups = 'organization' | 'country'\n *\n * export const analytics = new Analytics<MyAppEvents, MyUserData, MyAppGroups>({\n * app: appName,\n * version: revision,\n * debug: isDebug(),\n * })\n *\n * @example Use initialized analytics in your-app\n * import { Router } from 'next/router'\n * import { useEffect } from 'react'\n *\n * import { isValidCondoUIMessage } from '@open-condo/ui/events'\n *\n * import { analytics } from '@/domains/common/utils/analytics'\n * import { useAuth } from '@/domains/user/utils/auth'\n *\n * import type { FC } from 'react'\n *\n * export const ResidentAppEventsHandler: FC = () => {\n * const user = useAuth()\n * const { activeResident } = useActiveResident()\n *\n * // User change tracking\n * useEffect(() => {\n * if (user) {\n * analytics.identify(user.id, { name: user.name, type: user.type })\n * }\n * }, [user])\n *\n * // Page views tracking\n * useEffect(() => {\n * const handleRouteChange = () => analytics.pageView()\n * Router.events.on('routeChangeComplete', handleRouteChange)\n *\n * return () => {\n * Router.events.off('routeChangeComplete', handleRouteChange)\n * }\n * }, [])\n *\n * // Condo UI events tracking\n * useEffect(() => {\n * if (typeof window !== 'undefined') {\n * const handleMessage = async (e: MessageEvent) => {\n * if (isValidCondoUIMessage(e)) {\n * const { params: { event, ...eventData } } = e.data\n * await analytics.trackUntyped(event, eventData)\n * }\n *\n * }\n *\n * window.addEventListener('message', handleMessage)\n *\n * return () => {\n * window.removeEventListener('message', handleMessage)\n * }\n * }\n * }, [])\n *\n * return null\n * }\n */\nexport class Analytics<\n Events extends Record<string, AnyPayload> = Record<string, never>,\n UserData extends AnyPayload = Record<string, never>,\n GroupNames extends string = never,\n> {\n private readonly _analytics: AnalyticsInstanceWithGroups<GroupNames>\n private readonly _groups = new Set<GroupNames>()\n\n constructor (config: AnalyticsConfig) {\n this._analytics = DefaultAnalytics({\n ...config,\n plugins: [\n IdentityMiddlewarePlugin,\n GroupingMiddlewarePlugin,\n ...(config.plugins || []),\n ],\n }) as AnalyticsInstanceWithGroups<GroupNames>\n this._analytics.groups = this._groups\n }\n\n /**\n * Tracks type-safe business events. Recommended to use in most cases in app's codebase.\n * To add an event, modify \"Events\" generic.\n */\n async track<EventName extends Extract<keyof Events, string>>(eventName: EventName, eventData: Events[EventName]): Promise<void> {\n await this._analytics.track(eventName, eventData)\n }\n\n /**\n * Tracks untyped analytics events, used mainly for external sources (bridge / ui-kit / messages, etc.)\n * @deprecated It's not recommended to use this in your business logic, consider using typed \"track\" instead\n */\n async trackUntyped (eventName: string, eventData: AnyPayload): Promise<void> {\n await this._analytics.track(eventName, eventData)\n }\n\n /**\n * Tracks page changing in SPAs\n */\n async pageView (data?: PageData): Promise<void> {\n await this._analytics.page(data)\n }\n\n /**\n * Identifies user in analytics provider.\n * To specify all possible shape of user's data, modify \"UserData\" generic\n *\n * NOTE: Analytics plugins don't have a fixed behavior on how to handle consecutive identify calls.\n * Some of them affect only subsequent events, others affect all user events.\n * Therefore, it is not recommended to put cohort-specific data (organization, address, language, etc.) here.\n * Instead, use something like \"group\" method if your plugins supports it.\n */\n async identify<Key extends keyof UserData> (userId: string, userData?: Pick<UserData, Key>): Promise<void> {\n await this._analytics.identify(userId, userData)\n }\n\n /**\n * Resets analytics providers\n */\n async reset (): Promise<void> {\n for (const groupName of this._groups) {\n const groupKey = Analytics.getGroupKey(groupName)\n this._analytics.storage.removeItem(groupKey)\n }\n this._groups.clear()\n await this._analytics.reset()\n }\n\n static getGroupKey (groupName: string): string {\n return ['analytics', 'groups', groupName].join(':')\n }\n\n /**\n * Associates the user with a group, adding the attributes `groups.${groupName} = groupId`\n * to all subsequent analytic queries for the user\n * @example\n * analytics.setGroup('organization', organizationId)\n */\n setGroup (groupName: GroupNames, groupId: string): void {\n const groupKey = Analytics.getGroupKey(groupName)\n this._groups.add(groupName)\n this._analytics.storage.setItem(groupKey, groupId)\n }\n\n /**\n * Removes the current user from the group, stripping the “groups.${groupName}”\n * attribute from all subsequent eventualities\n * @example\n * deleteOrganization()\n * .then(() => analytics.removeGroup('organization'))\n */\n removeGroup (groupName: GroupNames): void {\n const groupKey = Analytics.getGroupKey(groupName)\n this._analytics.storage.removeItem(groupKey)\n this._groups.delete(groupName)\n }\n}\n","import { Analytics } from '../instance'\n\nimport type { AnalyticsPlugin, PluginTrackData } from '../types'\n\nfunction _addGroupingProperties (data: PluginTrackData): PluginTrackData {\n const { instance } = data\n\n for (const groupName of instance.groups) {\n const groupKey = Analytics.getGroupKey(groupName)\n const groupValue = instance.storage.getItem(groupKey)\n\n if (typeof groupValue === 'string') {\n const groupAttrName = `groups.${groupName}`\n data.payload.properties[groupAttrName] = groupValue\n }\n }\n return data\n}\n\nexport const GroupingMiddlewarePlugin: AnalyticsPlugin = {\n name: 'analytics-plugin-grouping',\n track: _addGroupingProperties,\n page: _addGroupingProperties,\n}","import type { AnalyticsPlugin, PluginTrackData } from '../types'\n\nconst IDENTITY_PROPERTIES = ['app', 'version']\n\nfunction _addIdentityProperties (data: PluginTrackData): PluginTrackData {\n const { instance } = data\n\n for (const contextPropertyName of IDENTITY_PROPERTIES) {\n const propertyValue = instance.getState(`context.${contextPropertyName}`)\n if (typeof propertyValue === 'string') {\n data.payload.properties[contextPropertyName] = propertyValue\n }\n }\n\n return data\n}\n\nexport const IdentityMiddlewarePlugin: AnalyticsPlugin = {\n name: 'analytics-plugin-identity',\n track: _addIdentityProperties,\n page: _addIdentityProperties,\n}","import { parse as parseCookieString, serialize as serializeCookie } from 'cookie'\nimport { setCookie, getCookies } from 'cookies-next'\n\nimport { getProxyHeadersForIp } from './proxying'\nimport { getRequestIp } from './proxying'\nimport {\n FINGERPRINT_ID_COOKIE_NAME,\n generateFingerprint,\n getClientSideFingerprint,\n} from './sender'\nimport { generateUUIDv4 } from './uuid'\n\nimport type { DefaultContext, RequestHandler } from '@apollo/client'\nimport type { IncomingMessage, ServerResponse } from 'http'\n\ntype Response = ServerResponse\n\ntype SSRContext = {\n headers: Record<string, string>\n defaultContext: DefaultContext\n}\n\nconst SSR_DEFAULT_FINGERPRINT = 'webAppSSR'\nconst COOKIE_HEADER_NAME = 'cookie'\nconst REMOTE_APP_HEADER_NAME = 'x-remote-app'\nconst REMOTE_VERSION_HEADER_NAME = 'x-remote-version'\nconst REMOTE_CLIENT_HEADER_NANE = 'x-remote-client'\nconst REMOTE_ENV_HEADER_NAME = 'x-remote-env'\nconst TARGET_HEADER_NAME = 'x-target'\nconst START_REQUEST_ID_HEADER_NAME = 'x-start-request-id'\nconst PARENT_REQUEST_ID_HEADER_NAME = 'x-parent-request-id'\n\nexport type TracingMiddlewareOptions = {\n serviceUrl: string\n codeVersion: string\n target?: string\n}\n\nexport type SSRProxyingMiddlewareOptions = {\n apiUrl: string\n proxyId?: string\n proxySecret?: string\n}\n\nfunction generateRequestId () {\n return `BR${generateUUIDv4().replaceAll('-', '')}`\n}\n\nexport function getTracingMiddleware (options: TracingMiddlewareOptions): RequestHandler {\n return function (operation, forward) {\n operation.setContext((previousContext: DefaultContext) => {\n const { headers: previousHeaders } = previousContext\n\n const reqId = generateRequestId()\n\n const headers = {\n ...previousHeaders,\n [REMOTE_APP_HEADER_NAME]: options.serviceUrl,\n [REMOTE_VERSION_HEADER_NAME]: options.codeVersion,\n [PARENT_REQUEST_ID_HEADER_NAME]: reqId,\n [START_REQUEST_ID_HEADER_NAME]: reqId,\n }\n\n if (options.target) {\n headers[TARGET_HEADER_NAME] = options.target\n }\n\n headers[REMOTE_ENV_HEADER_NAME] = typeof document === 'undefined' ? 'SSR' : 'CSR'\n\n\n\n // NOTE: CSR\n if (typeof document !== 'undefined' && document.cookie) {\n headers[REMOTE_CLIENT_HEADER_NANE] = getClientSideFingerprint()\n } else if (headers[COOKIE_HEADER_NAME]) {\n const ssrCookies = parseCookieString(headers[COOKIE_HEADER_NAME])\n\n headers[REMOTE_CLIENT_HEADER_NANE] = ssrCookies[FINGERPRINT_ID_COOKIE_NAME] || SSR_DEFAULT_FINGERPRINT\n }\n\n\n return {\n ...previousContext,\n headers,\n }\n })\n\n return forward(operation)\n }\n}\n\nexport function getSSRProxyingMiddleware ({ proxyId, proxySecret, apiUrl }: SSRProxyingMiddlewareOptions): RequestHandler {\n return function (operation, forward) {\n operation.setContext((previousContext: DefaultContext) => {\n if (typeof previousContext.clientIp !== 'string' || !proxyId || !proxySecret) return previousContext\n const proxyHeaders = getProxyHeadersForIp(\n 'POST',\n apiUrl,\n previousContext.clientIp,\n proxyId,\n proxySecret,\n )\n\n return {\n ...previousContext,\n headers: {\n ...previousContext.headers,\n ...proxyHeaders,\n },\n }\n })\n\n return forward(operation)\n }\n}\n\nexport function prepareSSRContext (req?: IncomingMessage, res?: Response): SSRContext {\n if (!req) {\n return {\n headers: {},\n defaultContext: {},\n }\n }\n\n const requestCookies = getCookies({ req, res })\n\n if (!requestCookies[FINGERPRINT_ID_COOKIE_NAME]) {\n const fingerprint = generateFingerprint()\n requestCookies[FINGERPRINT_ID_COOKIE_NAME] = fingerprint\n // NOTE: req and res are used to operate \"set-cookie\" headers\n setCookie(FINGERPRINT_ID_COOKIE_NAME, fingerprint, { req, res })\n }\n\n const cookieHeader = Object.entries(requestCookies)\n .map(([name, value]) => value ? serializeCookie(name, value) : null)\n .filter(Boolean)\n .join(';')\n\n const clientIp = getRequestIp(req, () => true)\n\n return {\n headers: {\n cookie: cookieHeader,\n },\n defaultContext: {\n clientIp,\n },\n }\n}\n","import jwt from 'jsonwebtoken'\nimport proxyAddr from 'proxy-addr'\nimport { z } from 'zod'\n\nimport type { IncomingMessage } from 'http'\n\ntype ProxyConfig = {\n address: string | Array<string>\n secret: string\n}\n\ntype ProxyId = string\n\nexport type KnownProxies = Record<ProxyId, ProxyConfig>\n\nexport type TrustProxyFunction = (proxyAddr: string, idx: number) => boolean\n\nconst _ipSchema = z.union([z.ipv4(), z.ipv6()])\nconst _timeStampBasicRegexp = /^\\d+$/\n\nconst DEFAULT_PROXY_TIMEOUT_IN_MS = 5_000 // 5 sec to pass request is enough\nconst X_PROXY_ID_HEADER = 'x-proxy-id' as const\nconst X_PROXY_IP_HEADER = 'x-proxy-ip' as const\nconst X_PROXY_TIMESTAMP_HEADER = 'x-proxy-timestamp' as const\nconst X_PROXY_SIGNATURE_HEADER = 'x-proxy-signature' as const\n\nexport type ProxyHeaders = {\n [X_PROXY_ID_HEADER]: string\n [X_PROXY_IP_HEADER]: string\n [X_PROXY_TIMESTAMP_HEADER]: string\n [X_PROXY_SIGNATURE_HEADER]: string\n}\n\nfunction _getTimestampFromHeader (timestamp: string) {\n if (!_timeStampBasicRegexp.test(timestamp)) return Number.NaN\n return (new Date(parseInt(timestamp))).getTime()\n}\n\nexport function getRequestIp (req: IncomingMessage, trustProxyFn: TrustProxyFunction, knownProxies?: KnownProxies): string {\n // NOTE: That's what express does under the hood: https://github.com/expressjs/express/blob/4.x/lib/request.js#L349\n const originalIP = proxyAddr(req, trustProxyFn)\n\n if (!knownProxies) return originalIP\n\n const xProxyId = req.headers[X_PROXY_ID_HEADER]\n const xProxyIp = req.headers[X_PROXY_IP_HEADER]\n // NOTE: used to prevent relay attacks\n const xProxyTimestamp = req.headers[X_PROXY_TIMESTAMP_HEADER]\n const xProxySignature = req.headers[X_PROXY_SIGNATURE_HEADER]\n\n if (\n typeof xProxyId !== 'string' ||\n typeof xProxyIp !== 'string' ||\n typeof xProxyTimestamp !== 'string' ||\n typeof xProxySignature !== 'string'\n ) {\n return originalIP\n }\n\n // NOTE: validate, that x-proxy-ip is correct IP\n const { success: isValidIp } = _ipSchema.safeParse(xProxyIp)\n if (!isValidIp) {\n return originalIP\n }\n\n // NOTE: validate timestamp: it should less than now and no more than 5s less than now (recent enough)\n const timestamp = _getTimestampFromHeader(xProxyTimestamp)\n const now = Date.now()\n if (\n Number.isNaN(timestamp) ||\n timestamp > now ||\n now - timestamp > DEFAULT_PROXY_TIMEOUT_IN_MS\n ) {\n return originalIP\n }\n\n // NOTE: validate signature and proxy IP\n if (!Object.hasOwn(knownProxies, xProxyId)) {\n return originalIP\n }\n const proxyConfig = knownProxies[xProxyId]\n const isRequestFromProxy = Array.isArray(proxyConfig.address)\n ? proxyConfig.address.includes(originalIP)\n : proxyConfig.address === originalIP\n if (!isRequestFromProxy) {\n return originalIP\n }\n\n try {\n // NOTE: config is passed from outside, where its obtained from .env, so its not hard-coded\n // nosemgrep: javascript.jsonwebtoken.security.jwt-hardcode.hardcoded-jwt-secret\n const jwtPayload = jwt.verify(xProxySignature, proxyConfig.secret, { algorithms: ['HS256'] })\n const expectedPayloadSchema = z.object({\n [X_PROXY_TIMESTAMP_HEADER]: z.literal(xProxyTimestamp),\n [X_PROXY_IP_HEADER]: z.literal(xProxyIp),\n [X_PROXY_ID_HEADER]: z.literal(xProxyId),\n method: z.literal(req.method),\n url: z.literal(req.url),\n })\n const { success: isMatchingSignature } = expectedPayloadSchema.safeParse(jwtPayload)\n\n return isMatchingSignature ? xProxyIp : originalIP\n } catch {\n return originalIP\n }\n}\n\nexport function getProxyHeadersForIp (method: string, url: string, ip: string, proxyId: string, secret: string): ProxyHeaders {\n const timestampString = String(Date.now())\n\n return {\n [X_PROXY_IP_HEADER]: ip,\n [X_PROXY_ID_HEADER]: proxyId,\n [X_PROXY_TIMESTAMP_HEADER]: timestampString,\n [X_PROXY_SIGNATURE_HEADER]: jwt.sign({\n [X_PROXY_IP_HEADER]: ip,\n [X_PROXY_ID_HEADER]: proxyId,\n [X_PROXY_TIMESTAMP_HEADER]: timestampString,\n method,\n url,\n }, secret, {\n expiresIn: Math.round(DEFAULT_PROXY_TIMEOUT_IN_MS / 1000),\n algorithm: 'HS256',\n }),\n }\n}\n\nexport function isRelativeUrl (url: string) {\n return url.startsWith('/')\n}\n\nexport function replaceUpstreamEndpoint ({\n endpoint,\n proxyPrefix,\n upstreamPrefix,\n upstreamOrigin,\n rewrites = {},\n}: {\n endpoint: string\n proxyPrefix: string\n upstreamPrefix: string\n upstreamOrigin: string\n rewrites?: Record<string, string>\n}) {\n const isRelativeLocation = isRelativeUrl(endpoint)\n const locationUrl = new URL(endpoint, 'https://_')\n\n let targetLocation\n\n const lookupUrl = new URL(endpoint, upstreamOrigin)\n lookupUrl.search = ''\n // First lookup relative location ('/some/path')\n if (isRelativeLocation || lookupUrl.origin === upstreamOrigin) {\n targetLocation ??= rewrites[lookupUrl.pathname]\n }\n\n // Then lookup absolute location ('https://upstreamhost.com/some/path')\n targetLocation ??= rewrites[lookupUrl.toString()]\n\n // If found lookup, perform smart replacement\n if (targetLocation) {\n const isRelativeTarget = isRelativeUrl(targetLocation)\n const targetUrl = new URL(targetLocation, upstreamOrigin)\n const targetSearchParams = new URLSearchParams(targetUrl.searchParams)\n // Replace target search params with location search params, then apply target search params on top\n if (!targetUrl.hash) {\n targetUrl.hash = locationUrl.hash\n }\n targetUrl.search = locationUrl.search\n for (const [name] of targetSearchParams.entries()) {\n targetUrl.searchParams.delete(name)\n }\n for (const [name, value] of targetSearchParams.entries()) {\n targetUrl.searchParams.append(name, value)\n }\n\n if (isRelativeTarget) {\n return targetUrl.pathname + targetUrl.search + targetUrl.hash\n } else {\n return targetUrl.toString()\n }\n }\n\n // If location is relative or has same as upstream domain, try to replace back upstreamPrefix with proxyPrefix\n if ((isRelativeLocation || locationUrl.origin === upstreamOrigin) && locationUrl.pathname.startsWith(upstreamPrefix)) {\n locationUrl.pathname = proxyPrefix + locationUrl.pathname.slice(upstreamPrefix.length)\n\n return locationUrl.pathname + locationUrl.search + locationUrl.hash\n }\n\n return endpoint\n}\n","import httpProxy from 'http-proxy'\n\nimport { getProxyHeadersForIp, getRequestIp, replaceUpstreamEndpoint } from './utils'\n\nimport type { KnownProxies, TrustProxyFunction } from './utils'\nimport type { IncomingMessage, ServerResponse } from 'http'\n\ntype IpProxyingOptions = {\n /** ID of the proxy to pass as x-proxy-id header */\n proxyId: string\n /** secret to sign x-proxy-signature header */\n proxySecret: string\n /** List of known proxies before current one from which IP can be extracted */\n knownProxies?: KnownProxies\n /**\n * Function to determine if a given IP address should be as x-forwarded-for header source.\n * Defaults to () => false, which means all IP addresses are trusted\n * */\n trustProxyFn?: TrustProxyFunction\n}\n\ntype LoggerType = {\n error: (data: unknown) => void\n}\n\ntype RelativeOrAbsoluteEndpoint = string\n\nexport type ProxyOptions = {\n /** Name of the proxy. Primarily used to set \"via\" header */\n name: string\n /** Proxy prefix which will be removed from request url */\n proxyPrefix: string\n /** Upstream host to proxy requests to */\n upstreamOrigin: string\n /** Upstream prefix to add to request url */\n upstreamPrefix: string\n /** IP proxying options, if specified, IP will be passed used signed x-proxy-id, x-proxy-ip, x-proxy-timestamp, x-proxy-signature headers */\n ipProxying?: IpProxyingOptions\n /** \n * Map of location header rewrites for redirects. \n * Key: upstream location, Value: rewritten location for client.\n * Used to rewrite Location headers in 3xx redirect responses.\n */\n locationRewrites?: Record<RelativeOrAbsoluteEndpoint, RelativeOrAbsoluteEndpoint>\n /** \n * Map of cookie path rewrites for Set-Cookie headers.\n * Key: upstream cookie path, Value: rewritten path for client.\n * Used to adjust cookie scope when proxying between different path prefixes.\n */\n cookiePathRewrites?: Record<RelativeOrAbsoluteEndpoint, RelativeOrAbsoluteEndpoint>\n /** \n * Logger instance for error reporting. Defaults to console if not provided.\n * Must implement an error method that accepts any data type.\n */\n logger?: LoggerType\n}\n\ntype ProxyHandler = (req: IncomingMessage, res: ServerResponse) => void\n\nexport function createProxy (options: ProxyOptions): ProxyHandler {\n const {\n name,\n proxyPrefix,\n upstreamOrigin,\n upstreamPrefix,\n ipProxying,\n locationRewrites,\n cookiePathRewrites,\n logger = console,\n } = options\n\n const proxy = httpProxy.createProxy({\n target: upstreamOrigin,\n changeOrigin: true,\n })\n\n const trustProxyFn = ipProxying?.trustProxyFn ?? (() => false)\n\n proxy.on('proxyReq', (proxyReq, req) => {\n if (req.url?.startsWith(proxyPrefix)) {\n proxyReq.path = upstreamPrefix + req.url.slice(proxyPrefix.length)\n }\n proxyReq.setHeader('via', name)\n\n if (req.url && req.method && ipProxying) {\n const ip = getRequestIp(req, trustProxyFn, ipProxying.knownProxies)\n const headers = getProxyHeadersForIp(req.method, proxyReq.path, ip, ipProxying.proxyId, ipProxying.proxySecret)\n for (const [headerName, headerValue] of Object.entries(headers)) {\n proxyReq.setHeader(headerName, headerValue)\n }\n }\n })\n proxy.on('proxyRes', (proxyRes, _req, _res) => {\n if (proxyRes.headers.location) {\n proxyRes.headers.location = replaceUpstreamEndpoint({\n endpoint: proxyRes.headers.location,\n proxyPrefix,\n upstreamPrefix,\n upstreamOrigin,\n rewrites: locationRewrites,\n })\n }\n\n // Handle Set-Cookie headers to rewrite cookie paths\n const setCookieHeaders = proxyRes.headers['set-cookie']\n if (setCookieHeaders) {\n proxyRes.headers['set-cookie'] = setCookieHeaders.map(cookieString => {\n return cookieString.replace(/;\\s*Path=([^;]+)/i, (match, pathValue) => {\n const rewrittenPath = replaceUpstreamEndpoint({\n endpoint: pathValue,\n proxyPrefix,\n upstreamPrefix,\n upstreamOrigin,\n rewrites: cookiePathRewrites,\n })\n return match.replace(pathValue, rewrittenPath)\n })\n })\n }\n })\n\n return function syncProxyHandler (req, res) {\n proxy.web(req, res, {}, (err) => {\n if (err) {\n // TODO: Add more complex loggers and standard error handling in next iterations\n logger.error({ msg: 'Proxy error', err })\n if (!res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ errors: [{ message: 'Proxy error' }] }))\n } else {\n res.end()\n }\n }\n })\n }\n}","import { getCookie, setCookie } from 'cookies-next'\n\nimport { generateUUIDv4 } from './uuid'\n\ntype SenderInfo = {\n dv: number\n fingerprint: string\n}\n\n/** Name of the cookie in which the fingerprint will be stored */\nexport const FINGERPRINT_ID_COOKIE_NAME = 'fingerprint'\n/** Default fingerprint length */\nexport const FINGERPRINT_ID_LENGTH = 12\n\nfunction makeId (length: number): string {\n const croppedLength = Math.min(length, 32)\n\n return generateUUIDv4().replaceAll('-', '').substring(0, croppedLength)\n}\n\nexport function generateFingerprint (): string {\n return makeId(FINGERPRINT_ID_LENGTH)\n}\n\n/**\n * Creates a device fingerprint in the browser environment\n * that can be used to send mutations in open-condo applications,\n * uses cookies for storage between sessions.\n * Mostly used to generate the sender field in getClientSideSenderInfo.\n * So consider using it instead\n */\nexport function getClientSideFingerprint (): string {\n let fingerprint = getCookie(FINGERPRINT_ID_COOKIE_NAME)\n if (!fingerprint) {\n fingerprint = generateFingerprint()\n setCookie(FINGERPRINT_ID_COOKIE_NAME, fingerprint)\n }\n\n return fingerprint\n}\n\n/**\n * Creates a device fingerprint in the browser environment\n * that can be used to send mutations in open-condo applications.\n * Uses cookies for storage between sessions\n * @example\n * submitReadingsMutation({\n * variables: {\n * data: {\n * ...values,\n * dv: 1,\n * sender: getClientSideSenderInfo(),\n * meter: { connect: { id: meter.id } },\n * source: { connect: { id: METER_READING_MOBILE_APP_SOURCE_ID } },\n * },\n * },\n * })\n */\nexport function getClientSideSenderInfo (): SenderInfo {\n return {\n dv: 1,\n fingerprint: getClientSideFingerprint(),\n }\n}\n","import { randomBytes } from 'crypto'\n\n/**\n * Generates v4 UUIDs in both browser and Node environments\n * @example\n * const uuid = generateUUIDv4()\n */\nexport function generateUUIDv4 (): string {\n let randomValues: Uint8Array\n\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n // Browser or Node.js (if Node 19+ supports crypto.randomUUID)\n return crypto.randomUUID()\n } else if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {\n // Browser environment\n randomValues = new Uint8Array(16)\n window.crypto.getRandomValues(randomValues)\n } else {\n // Node.js environment\n randomValues = randomBytes(16)\n }\n\n // Setting the version (4) and variant (RFC4122)\n randomValues[6] = (randomValues[6] & 0x0f) | 0x40 // version 4\n randomValues[8] = (randomValues[8] & 0x3f) | 0x80 // variant\n\n return [...randomValues]\n .map((value, index) => {\n const hex = value.toString(16).padStart(2, '0')\n if (index === 4 || index === 6 || index === 8 || index === 10) {\n return `-${hex}`\n }\n return hex\n })\n .join('')\n}\n","/**\n * Checks whenever values is NonNullable.\n * From es5 docs NonNullable excludes null and undefined from T\n * @example\n * const collection: Array<number | null | undefined> = [1, null, 3, undefined, 5]\n * const filtered = collection.filter(nonNull) // Array<number>, so it's safe to process it\n */\nexport function nonNull<TVal> (val: TVal): val is NonNullable<TVal> {\n return val !== null && val !== undefined\n}\n","import { getCookie } from 'cookies-next'\nimport { createContext, useContext } from 'react'\n\nimport type { IncomingMessage, ServerResponse } from 'http'\nimport type { Context } from 'react'\n\nconst SSR_COOKIES_DEFAULT_PROP_NAME = '__SSR_COOKIES__'\n\nexport type SSRCookiesContextValues<CookiesList extends ReadonlyArray<string>> = Record<CookiesList[number], string | null>\n\ntype Optional<T> = T | undefined\n\ntype SSRProps<PropsType extends Record<string, unknown>> = {\n props?: PropsType\n}\n\ntype SSRPropsWithCookies<\n PropsType extends Record<string, unknown>,\n CookiesList extends ReadonlyArray<string>,\n CookiesPropName extends string = typeof SSR_COOKIES_DEFAULT_PROP_NAME,\n> = {\n props: PropsType & {\n [K in CookiesPropName]?: SSRCookiesContextValues<CookiesList>\n }\n}\n\nexport type UseSSRCookiesExtractor<\n CookiesList extends ReadonlyArray<string>,\n CookiesPropName extends string = typeof SSR_COOKIES_DEFAULT_PROP_NAME,\n> = <PropsType extends Record<string, unknown>>(pageParams: SSRPropsWithCookies<PropsType, CookiesList, CookiesPropName>['props']) => SSRCookiesContextValues<CookiesList>\n\nexport type UseSSRCookies<CookiesList extends ReadonlyArray<string>> = () => SSRCookiesContextValues<CookiesList>\n\n/**\n * Helper that allows you to pass cookies from the request directly to the SSR,\n * thus avoiding layout shifts and loading states.\n *\n * NOTE: You should not use this tool to pass secure http-only cookies to the client,\n * that's why each application must define the list of allowed cookies itself.\n *\n * @example Init helper and export utils for app\n * import { SSRCookiesHelper } from '@open-condo/miniapp-utils/helpers/cookies'\n * import type { SSRCookiesContextValues } from '@open-condo/miniapp-utils/helpers/cookies'\n *\n * import type { Context } from 'react'\n *\n * // NOTE: put here only cookies needed in SRR (hydration), does not put http-only cookies here\n * const VITAL_COOKIES = ['residentId', 'isLayoutMinified'] as const\n *\n * const cookieHelper = new SSRCookiesHelper(VITAL_COOKIES)\n *\n * export const extractSSRCookies = cookieHelper.extractSSRCookies\n * export const useSSRCookiesExtractor = cookieHelper.generateUseSSRCookiesExtractorHook()\n * export const useSSRCookies = cookieHelper.generateUseSSRCookiesHook()\n * export const SSRCookiesContext = cookieHelper.getContext() as Context<SSRCookiesContextValues<typeof VITAL_COOKIES>>\n *\n * @example Extract cookies in getServerSideProps / getInitialProps\n * import { extractSSRCookies } from '@/domains/common/utils/ssr'\n *\n * export const getServerSideProps = async ({ req, res }) => {\n * return extractSSRCookies(req, res, {\n * props: { ... }\n * })\n * }\n *\n * @example Pass extracted cookies to React context in your _app.ts\n * import { SSRCookiesContext } from '@/domains/common/utils/ssr'\n *\n * export default function App ({ Component, pageProps }: AppProps): ReactNode {\n * const ssrCookies = useSSRCookiesExtractor(pageProps)\n *\n * return (\n * <SSRCookiesContext.Provider value={ssrCookies}>\n * <Component {...pageProps} />\n * </SSRCookiesContext.Provider>\n * )\n * }\n *\n * @example Use extracted cookies anywhere in your app.\n * // /domains/common/components/Layout.tsx\n * import { useState } from 'react'\n * import { useSSRCookies } from '@/domains/common/utils/ssr'\n *\n * import type { FC } from 'react'\n *\n * export const Layout: FC = () => {\n * const { isLayoutMinified } = useSSRCookies()\n *\n * const [layoutMinified, setLayoutMinified] = useState(isLayoutMinified === 'true')\n *\n * return {\n * // ...\n * }\n * }\n */\nexport class SSRCookiesHelper<\n CookiesList extends ReadonlyArray<string>,\n CookiesPropName extends string = typeof SSR_COOKIES_DEFAULT_PROP_NAME,\n> {\n allowedCookies: CookiesList\n propName: CookiesPropName\n private readonly context: Context<SSRCookiesContextValues<CookiesList>>\n private readonly defaultValues: SSRCookiesContextValues<CookiesList>\n\n constructor (allowedCookies: CookiesList, propName?: CookiesPropName) {\n this.allowedCookies = allowedCookies\n this.propName = propName || SSR_COOKIES_DEFAULT_PROP_NAME as CookiesPropName\n this.defaultValues = Object.fromEntries(allowedCookies.map(key => [key, null])) as SSRCookiesContextValues<CookiesList>\n this.context = createContext<SSRCookiesContextValues<CookiesList>>(this.defaultValues)\n\n this.extractSSRCookies = this.extractSSRCookies.bind(this)\n }\n\n getContext (): Context<SSRCookiesContextValues<CookiesList>> {\n return this.context\n }\n\n generateUseSSRCookiesExtractorHook (): UseSSRCookiesExtractor<CookiesList, CookiesPropName> {\n const defaultValues = this.defaultValues\n const propName = this.propName\n\n return function useSSRCookiesExtractor<PropsType extends Record<string, unknown>> (\n pageProps: SSRPropsWithCookies<PropsType, CookiesList, CookiesPropName>['props']\n ): SSRCookiesContextValues<CookiesList> {\n return pageProps[propName] || defaultValues\n }\n }\n\n generateUseSSRCookiesHook (): UseSSRCookies<CookiesList> {\n const context = this.context\n\n return function useSSRCookies (): SSRCookiesContextValues<CookiesList> {\n return useContext(context)\n }\n }\n\n extractSSRCookies<PropsType extends Record<string, unknown>> (\n req: Optional<IncomingMessage>,\n res: Optional<ServerResponse>,\n pageParams: SSRProps<PropsType>\n ): SSRPropsWithCookies<PropsType, CookiesList, CookiesPropName> {\n return {\n ...pageParams,\n props: {\n ...pageParams.props,\n [this.propName]: Object.fromEntries(\n Object.keys(this.defaultValues).map(key => [\n key,\n getCookie(key, { req, res }) || null,\n ])\n ),\n },\n } as SSRPropsWithCookies<PropsType, CookiesList, CookiesPropName>\n }\n}\n","/**\n * Check whether it's a server or client environment\n * @example\n * if (!isSSR()) {\n * console.log(window.location.href)\n * }\n */\nexport function isSSR (): boolean {\n return typeof window === 'undefined'\n}\n\n/**\n * Check whether it's development environment or not\n * @example\n * const IS_DEBUG_LOGS_ENABLED = isDebug()\n */\nexport function isDebug (): boolean {\n return process.env.NODE_ENV === 'development'\n}\n","type Rewrite = {\n source: string\n destination: string\n}\n\nconst POSTHOG_CLOUD_HOST_BASE = 'i.posthog.com'\nconst POSTHOG_CLOUD_HOST_MATCHER = new RegExp(`^(\\\\w+)\\\\.${POSTHOG_CLOUD_HOST_BASE.replaceAll('.', '\\\\.')}$`)\n\n/**\n * Gets posthog endpoint based on its domain and requested path.\n * Used to support both cloud and self-hosted instances, which differs in set of endpoints.\n * See example below for detailed explanation:\n * @example\n * getPosthogPath('https://eu.i.posthog.com', ['static', 'something']) // https://eu-assets.i.posthog.com/something\n * getPosthogPath('https://eu.i.posthog.com', ['other', 'path']) // https://eu.i.posthog.com/other/path\n * getPosthogPath('https://ph.self-hosted.com', ['static', 'something']) // https://ph.self-hosted.com/static/something\n * getPosthogPath('https://ph.self-hosted.com', ['other', 'path']) // https://ph.self-hosted.com/other/path\n */\nexport function getPosthogEndpoint (posthogDomain: string, requestedPath: Array<string>): string {\n const posthogURL = new URL(posthogDomain)\n\n const cloudMatch = posthogURL.host.match(POSTHOG_CLOUD_HOST_MATCHER)\n if (cloudMatch && cloudMatch.length > 1) {\n const region = cloudMatch[1]\n if (requestedPath.length && requestedPath[0] === 'static') {\n posthogURL.host = `${region}-assets.${POSTHOG_CLOUD_HOST_BASE}`\n posthogURL.pathname = requestedPath.slice(1).join('/')\n return posthogURL.toString()\n }\n }\n\n posthogURL.pathname = requestedPath.join('/')\n return posthogURL.toString()\n}\n\n/**\n * Generates Next.js rewrites based on PostHog domain,\n * so that PostHog can run in cloud and self-hosted versions without any ad blocker restrictions\n * @example\n * generateRewrites('https://eu.i.posthog.com', '/api/posthog')\n * generateRewrites('https://posthog.my.domain.com', '/api/posthog')\n * generateRewrites(process.env.NEXT_PUBLIC_POSTHOG_HOST, '/api/posthog')\n *\n * @deprecated This util is not used in condo applications and will be removed in next major upgrade,\n * since it requires knowing postHogDomain during build time. Consider setting up http proxy on API route instead\n */\nexport function generateRewrites (postHogDomain: string, routeEndpoint: string): Array<Rewrite> {\n const url = new URL(postHogDomain)\n\n const match = url.host.match(POSTHOG_CLOUD_HOST_MATCHER)\n\n // Cloud PH must have separate url for assets\n // SRC: https://posthog.com/docs/advanced/proxy/nextjs\n if (match && match.length > 1) {\n const region = match[1]\n return [\n {\n source: `${routeEndpoint}/static/:path*`,\n destination: `https://${region}-assets.${POSTHOG_CLOUD_HOST_BASE}/static/:path*`,\n },\n {\n source: `${routeEndpoint}/:path*`,\n destination: `https://${region}.${POSTHOG_CLOUD_HOST_BASE}/:path*`,\n },\n ]\n }\n\n return [\n {\n source: `${routeEndpoint}/:path*`,\n destination: `${postHogDomain}/:path*`,\n },\n ]\n}","// SRC: https://github.com/streamich/react-use/blob/master/src/useEffectOnce.ts\n\nimport { EffectCallback, useEffect } from 'react'\n\n/**\n * useEffect wrapper, that runs side effect only once on initial component render\n * @example\n * useEffectOnce(() => {\n * initAnalytics()\n * })\n */\nexport function useEffectOnce (cb: EffectCallback): void {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(cb, [])\n}\n","// Minified version of https://github.com/juliencrn/usehooks-ts/blob/master/packages/usehooks-ts/src/useIntersectionObserver/useIntersectionObserver.ts\n\nimport { useEffect, useRef, useState } from 'react'\n\ntype State = {\n isIntersecting: boolean\n entry?: IntersectionObserverEntry\n}\n\ntype UseIntersectionObserverOptions = {\n threshold?: number\n onChange?: (isIntersecting: boolean, entry: IntersectionObserverEntry) => void\n root?: Element | Document | null\n rootMargin?: string\n}\n\ntype IntersectionReturnType = {\n ref: (node?: Element | null) => void\n isIntersecting: boolean\n entry?: IntersectionObserverEntry\n}\n\nexport function useIntersectionObserver ({\n threshold = 0,\n root = null,\n rootMargin = '0%',\n onChange,\n}: UseIntersectionObserverOptions = {}): IntersectionReturnType {\n const [ref, setRef] = useState<Element | null | undefined>(null)\n const [state, setState] = useState<State>(() => ({\n isIntersecting: false,\n entry: undefined,\n }))\n\n const callbackRef = useRef<UseIntersectionObserverOptions['onChange']>()\n callbackRef.current = onChange\n\n useEffect(() => {\n if (!ref) return\n if (!('IntersectionObserver' in window)) return\n\n const observer = new IntersectionObserver((entries) => {\n const thresholds = observer.thresholds\n\n entries.forEach((entry) => {\n const isIntersecting =\n entry.isIntersecting &&\n thresholds.some(threshold => entry.intersectionRatio >= threshold)\n\n setState({ isIntersecting, entry })\n\n if (callbackRef.current) {\n callbackRef.current(isIntersecting, entry)\n }\n })\n }, { threshold, root, rootMargin })\n\n observer.observe(ref)\n\n return () => {\n observer.disconnect()\n }\n }, [ref, root, rootMargin, threshold])\n\n // ensures that if the observed element changes, the intersection observer is reinitialized\n const prevRef = useRef<Element | null>(null)\n\n useEffect(() => {\n if (\n !ref &&\n state.entry?.target &&\n prevRef.current !== state.entry.target\n ) {\n prevRef.current = state.entry.target\n setState({ isIntersecting: false, entry: undefined })\n }\n }, [ref, state.entry])\n\n return {\n entry: state.entry,\n ref: setRef,\n isIntersecting: state.isIntersecting,\n }\n}","// SRC: https://github.com/streamich/react-use/blob/master/src/usePrevious.ts\n\nimport { useEffect, useRef } from 'react'\n\n/**\n * Returns previous state value, useful for diff comparison\n * @example\n * const [count, setCount] = useState(0)\n * const prevCount = usePrevious(value)\n *\n * return (\n * <button onClick={() => setValue(current => current + 1)}>\n * Diff: {count - prevCount}\n * </button>\n * )\n */\nexport function usePrevious<T> (state: T): T | undefined {\n const ref = useRef<T>()\n\n // Runs all the times, but after rendering, so ref.current update happens after return\n useEffect(() => {\n ref.current = state\n })\n\n return ref.current\n}\n"],"mappings":"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