@reykjavik/webtools 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+ // END: Mock typing of CookieHub's script API
3
+ // --------------------------------------------------------------------------
4
+ //
5
+ // ---------------------------------------------------------------------------
6
+ const idToken = '[ACCOUNT_ID]';
7
+ const scriptUrlTemplate = process.env.NODE_ENV === 'production'
8
+ ? `https://cookiehub.net/c2/${idToken}.js`
9
+ : `https://cookiehub.net/dev/${idToken}.js`;
10
+ const CookieHubContext = createContext(undefined);
11
+ /**
12
+ * Used as the initial/default state when CookieHubProvider mounts and
13
+ * prepares to load the script
14
+ */
15
+ const initialConsentState = {
16
+ consent: {
17
+ analytics: false,
18
+ preferences: false,
19
+ marketing: false,
20
+ uncategorized: false,
21
+ },
22
+ };
23
+ /**
24
+ * Moves the CookieHub `<div/>` to the bottom of the dom tree
25
+ * for better accessability in screen readers.
26
+ */
27
+ const moveCookiehubScriptInDomTree = () => {
28
+ const cookieHubPromptElm = document.querySelector('.ch2');
29
+ if (cookieHubPromptElm) {
30
+ document.body.append(cookieHubPromptElm);
31
+ }
32
+ };
33
+ /**
34
+ * This context provider component loads and initialises the CookieHub consent
35
+ * management script and sets up a React state object with the relevant user
36
+ * consent flags.
37
+ *
38
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##cookiehubprovider-component
39
+ */
40
+ export const CookieHubProvider = (props) => {
41
+ const [state, setState] = useState(initialConsentState);
42
+ useEffect(() => {
43
+ const script = document.createElement('script');
44
+ script.async = true;
45
+ const opts = props.options || {};
46
+ script.src =
47
+ props.scriptUrl != null
48
+ ? props.scriptUrl
49
+ : scriptUrlTemplate.replace(idToken, props.accountId);
50
+ script.onload = () => {
51
+ window.cookiehub.load(Object.assign(Object.assign({}, opts), { onInitialise(status) {
52
+ const analytics = this.hasConsented('analytics');
53
+ const preferences = this.hasConsented('preferences');
54
+ const marketing = this.hasConsented('marketing');
55
+ const uncategorized = this.hasConsented('uncategorized');
56
+ // only trigger re-render if the consent is different from the default (all false)
57
+ if (analytics || preferences || marketing || uncategorized) {
58
+ setState({
59
+ consent: {
60
+ analytics,
61
+ preferences,
62
+ marketing,
63
+ uncategorized,
64
+ },
65
+ });
66
+ }
67
+ opts.onInitialise && opts.onInitialise.call(this, status);
68
+ },
69
+ onAllow(category) {
70
+ if (category === 'necessary') {
71
+ return;
72
+ }
73
+ setState((state) => (Object.assign(Object.assign({}, state), { consent: Object.assign(Object.assign({}, state.consent), { [category]: true }) })));
74
+ opts.onAllow && opts.onAllow.call(this, category);
75
+ },
76
+ onRevoke(category) {
77
+ if (category === 'necessary') {
78
+ return;
79
+ }
80
+ setState((state) => (Object.assign(Object.assign({}, state), { consent: Object.assign(Object.assign({}, state.consent), { [category]: false }) })));
81
+ opts.onAllow && opts.onAllow.call(this, category);
82
+ }, cookie: Object.assign({ secure: true }, opts.cookie) }));
83
+ moveCookiehubScriptInDomTree();
84
+ };
85
+ props.onError && (script.onerror = props.onError);
86
+ document.body.append(script);
87
+ },
88
+ // eslint-disable-next-line react-hooks/exhaustive-deps
89
+ []);
90
+ return (React.createElement(CookieHubContext.Provider, { value: state }, props.children));
91
+ };
92
+ // ---------------------------------------------------------------------------
93
+ /**
94
+ * Returns up-to-date cookie consent flags. For use in React components or hook
95
+ * functions.
96
+ *
97
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##usecookiehubconsent
98
+ */
99
+ export const useCookieHubConsent = () => { var _a; return ((_a = useContext(CookieHubContext)) === null || _a === void 0 ? void 0 : _a.consent) || {}; };
package/esm/http.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { ServerResponse } from 'http';
2
+ export declare const HTTP_200_OK = 200;
3
+ /**
4
+ * The request succeeded, and a new resource was created as a result.
5
+ * This is typically the response sent after POST requests,
6
+ * or some PUT requests.
7
+ **/
8
+ export declare const HTTP_201_Created = 201;
9
+ export declare const HTTP_202_Accepted = 202;
10
+ /**
11
+ * Only safe to use in response to GET and HEAD requests
12
+ *
13
+ * @deprecated Instead use `HTTP_308_PermanentRedirect`.
14
+ */
15
+ export declare const HTTP_301_MovedPermanently = 301;
16
+ /**
17
+ * Only safe to use in response to GET and HEAD requests
18
+ *
19
+ * @deprecated Instead use `HTTP_307_TemporaryRedirect`.
20
+ */
21
+ export declare const HTTP_302_Found = 302;
22
+ /** Use when POST or PUT successfully rediects to the created resource */
23
+ export declare const HTTP_303_SeeOther = 303;
24
+ /** Use in response GET and HEAD requests with `If-Modified-Since`/`If-None-Match` heaaders */
25
+ export declare const HTTP_304_NotModified = 304;
26
+ export declare const HTTP_307_TemporaryRedirect = 307;
27
+ export declare const HTTP_308_PermanentRedirect = 308;
28
+ /** The request is malformed (e.g. a URL param is of a wrong type) */
29
+ export declare const HTTP_400_BadRequest = 400;
30
+ /** User is not authenticated (i.e. not logged in) */
31
+ export declare const HTTP_401_Unauthorized = 401;
32
+ /** User is logged in but doesn't have the necessary privileges */
33
+ export declare const HTTP_403_Forbidden = 403;
34
+ /** The request looks OK but the resource does not exist */
35
+ export declare const HTTP_404_NotFound = 404;
36
+ /**
37
+ * The resource has been permanently deleted from server, with no forwarding
38
+ * address.
39
+ */
40
+ export declare const HTTP_410_Gone = 410;
41
+ /** The server refuses the attempt to brew coffee with a teapot. */
42
+ export declare const HTTP_418_ImATeapot = 418;
43
+ export declare const HTTP_500_InternalServerError = 500;
44
+ export type HTTP_SUCCESS = typeof HTTP_200_OK | typeof HTTP_201_Created | typeof HTTP_202_Accepted;
45
+ export type HTTP_REDIRECTION = typeof HTTP_301_MovedPermanently | typeof HTTP_302_Found | typeof HTTP_303_SeeOther | typeof HTTP_304_NotModified | typeof HTTP_307_TemporaryRedirect | typeof HTTP_308_PermanentRedirect;
46
+ export type HTTP_NOTMODIFIED = typeof HTTP_304_NotModified;
47
+ export type HTTP_CLIENT_ERROR = typeof HTTP_400_BadRequest | typeof HTTP_401_Unauthorized | typeof HTTP_403_Forbidden | typeof HTTP_404_NotFound | typeof HTTP_410_Gone | typeof HTTP_418_ImATeapot;
48
+ export type HTTP_NOT_FOUND = typeof HTTP_400_BadRequest | typeof HTTP_404_NotFound | typeof HTTP_410_Gone;
49
+ export type HTTP_BANNED = typeof HTTP_401_Unauthorized | typeof HTTP_403_Forbidden;
50
+ export type HTTP_SERVER_ERROR = typeof HTTP_500_InternalServerError;
51
+ export type HTTP_ERROR = HTTP_CLIENT_ERROR | HTTP_SERVER_ERROR;
52
+ export type HTTP_STATUS = HTTP_SUCCESS | HTTP_REDIRECTION | HTTP_CLIENT_ERROR | HTTP_SERVER_ERROR;
53
+ type TimeUnit = 's' | 'm' | 'h' | 'd' | 'w';
54
+ type TTL = number | `${number}${TimeUnit}`;
55
+ type TTLKeywords = 'permanent' | 'unset' | 'no-cache';
56
+ type TTLObj = {
57
+ /** Sets the cache `max-age=` for the resource. */
58
+ maxAge: TTL | TTLKeywords;
59
+ /** Sets `stale-while-revalidate=` for the resource */
60
+ staleWhileRevalidate?: TTL;
61
+ staleIfError?: TTL;
62
+ /** Sets the response caching as "public", instead of the default "private" */
63
+ publ?: boolean;
64
+ /** Sets a 'must-revalidate' flag instead of the default 'immutable' */
65
+ stability?: 'revalidate' | 'immutable' | 'normal';
66
+ };
67
+ /**
68
+ * Configures quick TTL-related settings for a HTTP request object
69
+ *
70
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1#type-ttlconfig
71
+ */
72
+ export type TTLConfig = TTL | TTLKeywords | TTLObj;
73
+ /**
74
+ * Use this function to quickly set the `Cache-Control` header with a `max-age=`
75
+ * on a HTTP response
76
+ *
77
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1#getcssbundleurl
78
+ */
79
+ export declare const cacheControl: (response: ServerResponse | {
80
+ res: ServerResponse;
81
+ }, ttlCfg: TTLConfig, eTag?: string | number) => void;
82
+ export {};
package/esm/http.js ADDED
@@ -0,0 +1,125 @@
1
+ export const HTTP_200_OK = 200;
2
+ /**
3
+ * The request succeeded, and a new resource was created as a result.
4
+ * This is typically the response sent after POST requests,
5
+ * or some PUT requests.
6
+ **/
7
+ export const HTTP_201_Created = 201;
8
+ /*
9
+ * The request has been received but not yet acted upon.
10
+ * Uee in cases where another process or server handles the request.
11
+ */
12
+ export const HTTP_202_Accepted = 202;
13
+ /**
14
+ * Only safe to use in response to GET and HEAD requests
15
+ *
16
+ * @deprecated Instead use `HTTP_308_PermanentRedirect`.
17
+ */
18
+ export const HTTP_301_MovedPermanently = 301;
19
+ /**
20
+ * Only safe to use in response to GET and HEAD requests
21
+ *
22
+ * @deprecated Instead use `HTTP_307_TemporaryRedirect`.
23
+ */
24
+ export const HTTP_302_Found = 302;
25
+ /** Use when POST or PUT successfully rediects to the created resource */
26
+ export const HTTP_303_SeeOther = 303;
27
+ /** Use in response GET and HEAD requests with `If-Modified-Since`/`If-None-Match` heaaders */
28
+ export const HTTP_304_NotModified = 304;
29
+ export const HTTP_307_TemporaryRedirect = 307;
30
+ export const HTTP_308_PermanentRedirect = 308;
31
+ /** The request is malformed (e.g. a URL param is of a wrong type) */
32
+ export const HTTP_400_BadRequest = 400;
33
+ /** User is not authenticated (i.e. not logged in) */
34
+ export const HTTP_401_Unauthorized = 401;
35
+ /** User is logged in but doesn't have the necessary privileges */
36
+ export const HTTP_403_Forbidden = 403;
37
+ /** The request looks OK but the resource does not exist */
38
+ export const HTTP_404_NotFound = 404;
39
+ /**
40
+ * The resource has been permanently deleted from server, with no forwarding
41
+ * address.
42
+ */
43
+ export const HTTP_410_Gone = 410;
44
+ /** The server refuses the attempt to brew coffee with a teapot. */
45
+ export const HTTP_418_ImATeapot = 418;
46
+ export const HTTP_500_InternalServerError = 500;
47
+ const unitToSeconds = {
48
+ s: 1,
49
+ m: 60,
50
+ h: 3600,
51
+ d: 24 * 3600,
52
+ w: 7 * 24 * 3600,
53
+ };
54
+ const toSec = (ttl) => {
55
+ if (ttl == null) {
56
+ return;
57
+ }
58
+ if (typeof ttl === 'string') {
59
+ const value = parseFloat(ttl);
60
+ const factor = unitToSeconds[ttl.slice(-1)] || 1;
61
+ ttl = value * factor;
62
+ }
63
+ return !isNaN(ttl) ? ttl : undefined;
64
+ };
65
+ const stabilities = {
66
+ revalidate: ', must-revalidate',
67
+ immutable: ', immutable',
68
+ normal: '',
69
+ };
70
+ const setCC = (response, cc) => {
71
+ const devModeHeader = 'X-Cache-Control';
72
+ // Also set `X-Cache-Control` in dev mode, because some frameworks
73
+ // **cough** **nextjs** **cough** forcefully override the `Cache-Control`
74
+ // header when the server is in dev mode.
75
+ if (!cc) {
76
+ response.removeHeader('Cache-Control');
77
+ process.env.NODE_ENV !== 'production' && response.removeHeader(devModeHeader);
78
+ return;
79
+ }
80
+ response.setHeader('Cache-Control', cc);
81
+ process.env.NODE_ENV !== 'production' && response.setHeader(devModeHeader, cc);
82
+ };
83
+ /**
84
+ * Use this function to quickly set the `Cache-Control` header with a `max-age=`
85
+ * on a HTTP response
86
+ *
87
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1#getcssbundleurl
88
+ */
89
+ // eslint-disable-next-line complexity
90
+ export const cacheControl = (response, ttlCfg, eTag) => {
91
+ response = 'res' in response ? response.res : response;
92
+ const opts = typeof ttlCfg === 'number' || typeof ttlCfg === 'string'
93
+ ? { maxAge: ttlCfg }
94
+ : ttlCfg;
95
+ let maxAge = opts.maxAge;
96
+ if (typeof maxAge === 'string') {
97
+ if (maxAge === 'permanent') {
98
+ maxAge = 365 * unitToSeconds.d;
99
+ }
100
+ else if (maxAge === 'no-cache') {
101
+ maxAge = 0;
102
+ }
103
+ else if (maxAge === 'unset') {
104
+ maxAge = undefined;
105
+ }
106
+ }
107
+ maxAge = toSec(maxAge);
108
+ if (maxAge == null) {
109
+ response.removeHeader('Cache-Control');
110
+ return;
111
+ }
112
+ const sWR_ttl = toSec(opts.staleWhileRevalidate);
113
+ const sWR = sWR_ttl != null ? `, stale-while-revalidate=${sWR_ttl}` : '';
114
+ const sIE_ttl = toSec(opts.staleIfError);
115
+ const sIE = sIE_ttl != null ? `, stale-if-error=${sIE_ttl}` : '';
116
+ maxAge = Math.round(maxAge);
117
+ if (maxAge <= 0) {
118
+ setCC(response, 'no-cache');
119
+ return;
120
+ }
121
+ const scope = opts.publ ? 'public' : 'private';
122
+ const stability = (opts.stability && stabilities[opts.stability]) || stabilities.immutable;
123
+ eTag != null && response.setHeader('ETag', eTag);
124
+ setCC(response, `${scope}, max-age=${maxAge + sWR + sIE + stability}`);
125
+ };
package/esm/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /// <reference path="./CookieHubConsent.d.tsx" />
2
+ /// <reference path="./http.d.ts" />
3
+ /// <reference path="./next/http.d.tsx" />
4
+ /// <reference path="./next/SiteImprove.d.tsx" />
5
+
6
+ export {};
package/esm/index.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,81 @@
1
+ /// <reference types="react" />
2
+ import { EitherObj } from '@reykjavik/hanna-utils';
3
+ declare global {
4
+ interface Window {
5
+ _sz?: Array<SiteImproveEvent> & {
6
+ /**
7
+ * Set if posting a tracking event is attempted before SiteImprove has
8
+ * been initialized, and the window._sz array had to be initialized
9
+ * just-in-time.
10
+ */
11
+ _jit_defined_?: true;
12
+ };
13
+ }
14
+ }
15
+ type SiteImproveEvent = SiteImprovePageView | SiteImproveCustomEvent;
16
+ type SiteImprovePageView = [
17
+ type: 'trackdynamic',
18
+ data: {
19
+ /** New page URL */
20
+ url: string;
21
+ /** The previous (referer) URL */
22
+ ref: string;
23
+ /** New page title */
24
+ title?: string;
25
+ }
26
+ ];
27
+ type SiteImproveCustomEvent = [
28
+ type: 'event',
29
+ category: string,
30
+ action: string,
31
+ label?: string
32
+ ];
33
+ export type SiteImproveProps = EitherObj<{
34
+ /**
35
+ * Your SiteImprove account ID.
36
+ *
37
+ * It's a random-looking numerical string, and it can usually be
38
+ * extracted from the script embed URL like this:
39
+ * `"https://siteimproveanalytics.com/js/siteanalyze_[ACCOUNT_ID].js"`
40
+ */
41
+ accountId: string;
42
+ }, {
43
+ /**
44
+ * The full SiteImprove analytics script URL.
45
+ *
46
+ * Something like `"https://siteimproveanalytics.com/js/siteanalyze_[ACCOUNT_ID].js"`
47
+ */
48
+ scriptUrl: string;
49
+ }> & {
50
+ /**
51
+ * Manual GDPR 'analytics' consent flag.
52
+ *
53
+ * A value of `false` prevents the analytics script being loaded.
54
+ *
55
+ * Any other value causes the component to defer to the `CookieHubProvider`
56
+ * component, and only applies if the cookiehub flag is undefined.
57
+ */
58
+ hasConstented?: boolean;
59
+ /**
60
+ * Custom callback for when the SiteImprove script has loaded.
61
+ */
62
+ onLoad?: (e: unknown) => void;
63
+ /**
64
+ * Error callback for if the SiteImprove script fails to load.
65
+ */
66
+ onError?: (e: unknown) => void;
67
+ };
68
+ /**
69
+ * A component for loading a SiteImprove analytics script and set up page-view
70
+ * tracking across Next.js routes.
71
+ *
72
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##siteimprove-component
73
+ */
74
+ export declare const SiteImprove: (props: SiteImproveProps) => JSX.Element | null;
75
+ /**
76
+ * A small helper for tracking custom UI events and reporting them to SiteImrove.
77
+ *
78
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimprove-helper
79
+ */
80
+ export declare const pingSiteImprove: (category: string, action: string, label?: string) => void;
81
+ export {};
@@ -0,0 +1,100 @@
1
+ import React, { useEffect } from 'react';
2
+ import { Router } from 'next/router';
3
+ import Script from 'next/script';
4
+ import { useCookieHubConsent } from '../CookieHubConsent';
5
+ // END: Mock typing of SiteImprove's event tracking API
6
+ // --------------------------------------------------------------------------
7
+ //
8
+ // ---------------------------------------------------------------------------
9
+ const _emitEvent = typeof window === 'undefined'
10
+ ? () => undefined
11
+ : (event) => {
12
+ let _sz = window._sz;
13
+ if (!_sz) {
14
+ _sz = window._sz = [];
15
+ _sz._jit_defined_ = true;
16
+ }
17
+ if (process.env.NODE_ENV === 'development') {
18
+ console.info('SiteImprove:', event);
19
+ }
20
+ else {
21
+ _sz.push(event);
22
+ }
23
+ };
24
+ /*
25
+ SiteImprove's "trackdynamic" (page view) event requires both the new URL
26
+ and the old (referer) URL.
27
+ Next router's `routeChangeComplete` (which is the correct point in time
28
+ to send the tracking event) does not provide access to the previous URL,
29
+ so we need to capture it during `routeChangeStart`.
30
+ We feel it's safe to assume that every `routeChangeComplete` event
31
+ always fires directly after its `routeChangeStart` counterpart,
32
+ and it is thus safe to simply store the old URL in a local variable.
33
+ This may look dodgy, but should prove reasonably safe in practice.
34
+ */
35
+ let refUrl = '';
36
+ const captureRefUrl = () => {
37
+ refUrl = document.location.pathname + document.location.search;
38
+ };
39
+ const sendRoutingEvent = (url) => _emitEvent([
40
+ 'trackdynamic',
41
+ {
42
+ url,
43
+ ref: refUrl,
44
+ title: document.title,
45
+ },
46
+ ]);
47
+ // ---------------------------------------------------------------------------
48
+ const idToken = '[ACCOUNT_ID]';
49
+ const scriptUrlTemplate = `https://siteimproveanalytics.com/js/siteanalyze_${idToken}.js`;
50
+ /**
51
+ * A component for loading a SiteImprove analytics script and set up page-view
52
+ * tracking across Next.js routes.
53
+ *
54
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##siteimprove-component
55
+ */
56
+ export const SiteImprove = (props) => {
57
+ const { analytics } = useCookieHubConsent();
58
+ const consented = (analytics && props.hasConstented !== false) ||
59
+ (analytics === undefined && props.hasConstented);
60
+ useEffect(() => {
61
+ if (consented) {
62
+ const routerEvents = Router.events;
63
+ routerEvents.on('routeChangeStart', captureRefUrl);
64
+ routerEvents.on('routeChangeComplete', sendRoutingEvent);
65
+ return () => {
66
+ routerEvents.off('routeChangeStart', captureRefUrl);
67
+ routerEvents.off('routeChangeComplete', sendRoutingEvent);
68
+ };
69
+ }
70
+ }, [consented]);
71
+ if (!consented) {
72
+ return null;
73
+ }
74
+ if (process.env.NODE_ENV !== 'production') {
75
+ console.info('Mock loading SiteImprove in development mode.');
76
+ if (!window._sz) {
77
+ setTimeout(() => {
78
+ window._sz = window._sz || [];
79
+ }, 300);
80
+ }
81
+ return null;
82
+ }
83
+ const scriptUrl = props.scriptUrl != null
84
+ ? props.scriptUrl
85
+ : scriptUrlTemplate.replace(idToken, props.accountId);
86
+ return (React.createElement(Script, { type: "text/javascript", strategy: "afterInteractive", src: scriptUrl, onLoad: props.onLoad, onError: props.onError }));
87
+ };
88
+ // ---------------------------------------------------------------------------
89
+ /**
90
+ * A small helper for tracking custom UI events and reporting them to SiteImrove.
91
+ *
92
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimprove-helper
93
+ */
94
+ export const pingSiteImprove = (category, action, label) => {
95
+ if (process.env.NODE_ENV === 'development' &&
96
+ (!window._sz || window._sz._jit_defined_)) {
97
+ console.warn('`pingSiteImprove` Was called before SiteImprove script was loaded.');
98
+ }
99
+ _emitEvent(['event', category, action, label]);
100
+ };
@@ -0,0 +1,56 @@
1
+ /// <reference types="node" />
2
+ import React, { FunctionComponent } from 'react';
3
+ import { Cleanup } from '@reykjavik/hanna-utils';
4
+ import { ServerResponse } from 'http';
5
+ import type { AppType } from 'next/app';
6
+ import type { HTTP_ERROR, TTLConfig } from '../http';
7
+ export * from '../http';
8
+ type NextContextLike = {
9
+ res: ServerResponse;
10
+ };
11
+ export type ErrorProps = {
12
+ statusCode: HTTP_ERROR;
13
+ /** If a HTTP_ERROR code is passed, a default error message is displayed */
14
+ message?: string;
15
+ };
16
+ type ErrorizedPageProps<EP extends ErrorProps = ErrorProps> = {
17
+ __error: ErrorProps;
18
+ } & Omit<EP, keyof ErrorProps>;
19
+ type ShowErrorPageFn<EP extends ErrorProps = ErrorProps> = (response: ServerResponse | NextContextLike, error: (ErrorProps extends EP ? HTTP_ERROR : never) | EP,
20
+ /** Defaults to `"2s"`. Gets forwarded on to the `cacheControl` helper from `@reykjavik/webtools/http` */
21
+ ttl?: TTLConfig) => {
22
+ props: ErrorizedPageProps<EP>;
23
+ };
24
+ export type InferErrorPageProps<SEP extends ShowErrorPageFn<any>> = Cleanup<ReturnType<SEP>['props']>;
25
+ /**
26
+ * Hhigher-order component (HOC) factory for Next.js App compnents to handle
27
+ * cases when `getServerSideProps` returns an `__error` prop with `statusCode`
28
+ * and optional friendly `message`.
29
+ *
30
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1#makeerrorizeapphoc
31
+ */
32
+ export declare const makeErrorizeAppHOC: <EP extends Partial<ErrorProps>>(ErrorPage: React.FunctionComponent<EP>) => {
33
+ <P extends {
34
+ [key: string]: unknown;
35
+ __error?: undefined;
36
+ }>(App: AppType<P>): React.FunctionComponent<import("next/dist/shared/lib/utils").AppPropsType<any, P | ({
37
+ __error: ErrorProps;
38
+ } & Omit<ErrorProps & EP, keyof ErrorProps>)>> & {
39
+ getInitialProps?(context: import("next/dist/shared/lib/utils").AppContextType<import("next/router").NextRouter>): P | ({
40
+ __error: ErrorProps;
41
+ } & Omit<ErrorProps & EP, keyof ErrorProps>) | Promise<P | ({
42
+ __error: ErrorProps;
43
+ } & Omit<ErrorProps & EP, keyof ErrorProps>)>;
44
+ };
45
+ showErrorPage: ShowErrorPageFn<ErrorProps & EP>;
46
+ };
47
+ /**
48
+ * Use this method to inside a `getServerSideProps` method (or API route)
49
+ * to return an `HTTP_304_NotModified` response with an empty props object,
50
+ * in a way that doesn't make TypeScript at you.
51
+ *
52
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1#notmodified304-helper
53
+ */
54
+ export declare const notModified304: (response: ServerResponse | NextContextLike) => {
55
+ readonly props: any;
56
+ };
@@ -0,0 +1,81 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import React from 'react';
13
+ import { cacheControl, HTTP_304_NotModified } from '../http';
14
+ /*
15
+ Re-export all of the base [http module](#reykjavikwebtoolshttp)'s exports,
16
+ purely for convenience.
17
+ */
18
+ export * from '../http';
19
+ /**
20
+ * Use this method inside a `getServerSideProps` method (or API route)
21
+ * to return an error page with proper HTTP status code and all the shit.
22
+ *
23
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1#showerrorpage-helper
24
+ */
25
+ const showErrorPage = (response, error, ttl = '2s') => {
26
+ error =
27
+ typeof error === 'number'
28
+ ? { statusCode: error }
29
+ : error;
30
+ const { statusCode, message } = error, otherProps = __rest(error, ["statusCode", "message"]);
31
+ response = 'res' in response ? response.res : response;
32
+ response.statusCode = error.statusCode;
33
+ cacheControl(response, ttl);
34
+ return {
35
+ props: Object.assign(Object.assign({}, otherProps), { __error: { statusCode, message } }),
36
+ };
37
+ };
38
+ // ===========================================================================
39
+ /**
40
+ * Hhigher-order component (HOC) factory for Next.js App compnents to handle
41
+ * cases when `getServerSideProps` returns an `__error` prop with `statusCode`
42
+ * and optional friendly `message`.
43
+ *
44
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1#makeerrorizeapphoc
45
+ */
46
+ export const makeErrorizeAppHOC = (ErrorPage) => {
47
+ // the HOC
48
+ const withErrorHandling = (App) => {
49
+ const ErrorizedApp = (appProps) => {
50
+ const { pageProps } = appProps;
51
+ if (pageProps.__error) {
52
+ const { __error } = pageProps, otherProps = __rest(pageProps, ["__error"]);
53
+ return (React.createElement(App, Object.assign({}, appProps, { Component: ErrorPage, pageProps: Object.assign(Object.assign({}, otherProps), __error) })));
54
+ }
55
+ return React.createElement(App, Object.assign({}, appProps));
56
+ };
57
+ ErrorizedApp.getInitialProps = App.getInitialProps;
58
+ ErrorizedApp.displayName = 'Errorized' + (App.displayName || App.name || 'App');
59
+ return ErrorizedApp;
60
+ };
61
+ withErrorHandling.showErrorPage = showErrorPage;
62
+ return withErrorHandling;
63
+ };
64
+ // ===========================================================================
65
+ /**
66
+ * Use this method to inside a `getServerSideProps` method (or API route)
67
+ * to return an `HTTP_304_NotModified` response with an empty props object,
68
+ * in a way that doesn't make TypeScript at you.
69
+ *
70
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1#notmodified304-helper
71
+ */
72
+ export const notModified304 = (response) => {
73
+ response = 'res' in response ? response.res : response;
74
+ response.statusCode = HTTP_304_NotModified;
75
+ return {
76
+ props:
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ {},
79
+ };
80
+ };
81
+ // ---------------------------------------------------------------------------
@@ -0,0 +1 @@
1
+ {"type":"module"}