@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.
- package/CHANGELOG.md +11 -0
- package/CookieHubConsent.d.ts +231 -0
- package/CookieHubConsent.js +127 -0
- package/README.md +452 -0
- package/esm/CookieHubConsent.d.ts +231 -0
- package/esm/CookieHubConsent.js +99 -0
- package/esm/http.d.ts +82 -0
- package/esm/http.js +125 -0
- package/esm/index.d.ts +6 -0
- package/esm/index.js +1 -0
- package/esm/next/SiteImprove.d.ts +81 -0
- package/esm/next/SiteImprove.js +100 -0
- package/esm/next/http.d.ts +56 -0
- package/esm/next/http.js +81 -0
- package/esm/package.json +1 -0
- package/http.d.ts +82 -0
- package/http.js +129 -0
- package/index.d.ts +6 -0
- package/index.js +2 -0
- package/next/SiteImprove.d.ts +81 -0
- package/next/SiteImprove.js +131 -0
- package/next/http.d.ts +56 -0
- package/next/http.js +103 -0
- package/package.json +47 -0
package/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/http.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cacheControl = exports.HTTP_500_InternalServerError = exports.HTTP_418_ImATeapot = exports.HTTP_410_Gone = exports.HTTP_404_NotFound = exports.HTTP_403_Forbidden = exports.HTTP_401_Unauthorized = exports.HTTP_400_BadRequest = exports.HTTP_308_PermanentRedirect = exports.HTTP_307_TemporaryRedirect = exports.HTTP_304_NotModified = exports.HTTP_303_SeeOther = exports.HTTP_302_Found = exports.HTTP_301_MovedPermanently = exports.HTTP_202_Accepted = exports.HTTP_201_Created = exports.HTTP_200_OK = void 0;
|
|
4
|
+
exports.HTTP_200_OK = 200;
|
|
5
|
+
/**
|
|
6
|
+
* The request succeeded, and a new resource was created as a result.
|
|
7
|
+
* This is typically the response sent after POST requests,
|
|
8
|
+
* or some PUT requests.
|
|
9
|
+
**/
|
|
10
|
+
exports.HTTP_201_Created = 201;
|
|
11
|
+
/*
|
|
12
|
+
* The request has been received but not yet acted upon.
|
|
13
|
+
* Uee in cases where another process or server handles the request.
|
|
14
|
+
*/
|
|
15
|
+
exports.HTTP_202_Accepted = 202;
|
|
16
|
+
/**
|
|
17
|
+
* Only safe to use in response to GET and HEAD requests
|
|
18
|
+
*
|
|
19
|
+
* @deprecated Instead use `HTTP_308_PermanentRedirect`.
|
|
20
|
+
*/
|
|
21
|
+
exports.HTTP_301_MovedPermanently = 301;
|
|
22
|
+
/**
|
|
23
|
+
* Only safe to use in response to GET and HEAD requests
|
|
24
|
+
*
|
|
25
|
+
* @deprecated Instead use `HTTP_307_TemporaryRedirect`.
|
|
26
|
+
*/
|
|
27
|
+
exports.HTTP_302_Found = 302;
|
|
28
|
+
/** Use when POST or PUT successfully rediects to the created resource */
|
|
29
|
+
exports.HTTP_303_SeeOther = 303;
|
|
30
|
+
/** Use in response GET and HEAD requests with `If-Modified-Since`/`If-None-Match` heaaders */
|
|
31
|
+
exports.HTTP_304_NotModified = 304;
|
|
32
|
+
exports.HTTP_307_TemporaryRedirect = 307;
|
|
33
|
+
exports.HTTP_308_PermanentRedirect = 308;
|
|
34
|
+
/** The request is malformed (e.g. a URL param is of a wrong type) */
|
|
35
|
+
exports.HTTP_400_BadRequest = 400;
|
|
36
|
+
/** User is not authenticated (i.e. not logged in) */
|
|
37
|
+
exports.HTTP_401_Unauthorized = 401;
|
|
38
|
+
/** User is logged in but doesn't have the necessary privileges */
|
|
39
|
+
exports.HTTP_403_Forbidden = 403;
|
|
40
|
+
/** The request looks OK but the resource does not exist */
|
|
41
|
+
exports.HTTP_404_NotFound = 404;
|
|
42
|
+
/**
|
|
43
|
+
* The resource has been permanently deleted from server, with no forwarding
|
|
44
|
+
* address.
|
|
45
|
+
*/
|
|
46
|
+
exports.HTTP_410_Gone = 410;
|
|
47
|
+
/** The server refuses the attempt to brew coffee with a teapot. */
|
|
48
|
+
exports.HTTP_418_ImATeapot = 418;
|
|
49
|
+
exports.HTTP_500_InternalServerError = 500;
|
|
50
|
+
const unitToSeconds = {
|
|
51
|
+
s: 1,
|
|
52
|
+
m: 60,
|
|
53
|
+
h: 3600,
|
|
54
|
+
d: 24 * 3600,
|
|
55
|
+
w: 7 * 24 * 3600,
|
|
56
|
+
};
|
|
57
|
+
const toSec = (ttl) => {
|
|
58
|
+
if (ttl == null) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (typeof ttl === 'string') {
|
|
62
|
+
const value = parseFloat(ttl);
|
|
63
|
+
const factor = unitToSeconds[ttl.slice(-1)] || 1;
|
|
64
|
+
ttl = value * factor;
|
|
65
|
+
}
|
|
66
|
+
return !isNaN(ttl) ? ttl : undefined;
|
|
67
|
+
};
|
|
68
|
+
const stabilities = {
|
|
69
|
+
revalidate: ', must-revalidate',
|
|
70
|
+
immutable: ', immutable',
|
|
71
|
+
normal: '',
|
|
72
|
+
};
|
|
73
|
+
const setCC = (response, cc) => {
|
|
74
|
+
const devModeHeader = 'X-Cache-Control';
|
|
75
|
+
// Also set `X-Cache-Control` in dev mode, because some frameworks
|
|
76
|
+
// **cough** **nextjs** **cough** forcefully override the `Cache-Control`
|
|
77
|
+
// header when the server is in dev mode.
|
|
78
|
+
if (!cc) {
|
|
79
|
+
response.removeHeader('Cache-Control');
|
|
80
|
+
process.env.NODE_ENV !== 'production' && response.removeHeader(devModeHeader);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
response.setHeader('Cache-Control', cc);
|
|
84
|
+
process.env.NODE_ENV !== 'production' && response.setHeader(devModeHeader, cc);
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Use this function to quickly set the `Cache-Control` header with a `max-age=`
|
|
88
|
+
* on a HTTP response
|
|
89
|
+
*
|
|
90
|
+
* @see https://github.com/reykjavikcity/webtools/tree/v0.1#getcssbundleurl
|
|
91
|
+
*/
|
|
92
|
+
// eslint-disable-next-line complexity
|
|
93
|
+
const cacheControl = (response, ttlCfg, eTag) => {
|
|
94
|
+
response = 'res' in response ? response.res : response;
|
|
95
|
+
const opts = typeof ttlCfg === 'number' || typeof ttlCfg === 'string'
|
|
96
|
+
? { maxAge: ttlCfg }
|
|
97
|
+
: ttlCfg;
|
|
98
|
+
let maxAge = opts.maxAge;
|
|
99
|
+
if (typeof maxAge === 'string') {
|
|
100
|
+
if (maxAge === 'permanent') {
|
|
101
|
+
maxAge = 365 * unitToSeconds.d;
|
|
102
|
+
}
|
|
103
|
+
else if (maxAge === 'no-cache') {
|
|
104
|
+
maxAge = 0;
|
|
105
|
+
}
|
|
106
|
+
else if (maxAge === 'unset') {
|
|
107
|
+
maxAge = undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
maxAge = toSec(maxAge);
|
|
111
|
+
if (maxAge == null) {
|
|
112
|
+
response.removeHeader('Cache-Control');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const sWR_ttl = toSec(opts.staleWhileRevalidate);
|
|
116
|
+
const sWR = sWR_ttl != null ? `, stale-while-revalidate=${sWR_ttl}` : '';
|
|
117
|
+
const sIE_ttl = toSec(opts.staleIfError);
|
|
118
|
+
const sIE = sIE_ttl != null ? `, stale-if-error=${sIE_ttl}` : '';
|
|
119
|
+
maxAge = Math.round(maxAge);
|
|
120
|
+
if (maxAge <= 0) {
|
|
121
|
+
setCC(response, 'no-cache');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const scope = opts.publ ? 'public' : 'private';
|
|
125
|
+
const stability = (opts.stability && stabilities[opts.stability]) || stabilities.immutable;
|
|
126
|
+
eTag != null && response.setHeader('ETag', eTag);
|
|
127
|
+
setCC(response, `${scope}, max-age=${maxAge + sWR + sIE + stability}`);
|
|
128
|
+
};
|
|
129
|
+
exports.cacheControl = cacheControl;
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -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,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.pingSiteImprove = exports.SiteImprove = void 0;
|
|
30
|
+
const react_1 = __importStar(require("react"));
|
|
31
|
+
const router_1 = require("next/router");
|
|
32
|
+
const script_1 = __importDefault(require("next/script"));
|
|
33
|
+
const CookieHubConsent_1 = require("../CookieHubConsent");
|
|
34
|
+
// END: Mock typing of SiteImprove's event tracking API
|
|
35
|
+
// --------------------------------------------------------------------------
|
|
36
|
+
//
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
const _emitEvent = typeof window === 'undefined'
|
|
39
|
+
? () => undefined
|
|
40
|
+
: (event) => {
|
|
41
|
+
let _sz = window._sz;
|
|
42
|
+
if (!_sz) {
|
|
43
|
+
_sz = window._sz = [];
|
|
44
|
+
_sz._jit_defined_ = true;
|
|
45
|
+
}
|
|
46
|
+
if (process.env.NODE_ENV === 'development') {
|
|
47
|
+
console.info('SiteImprove:', event);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
_sz.push(event);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
/*
|
|
54
|
+
SiteImprove's "trackdynamic" (page view) event requires both the new URL
|
|
55
|
+
and the old (referer) URL.
|
|
56
|
+
Next router's `routeChangeComplete` (which is the correct point in time
|
|
57
|
+
to send the tracking event) does not provide access to the previous URL,
|
|
58
|
+
so we need to capture it during `routeChangeStart`.
|
|
59
|
+
We feel it's safe to assume that every `routeChangeComplete` event
|
|
60
|
+
always fires directly after its `routeChangeStart` counterpart,
|
|
61
|
+
and it is thus safe to simply store the old URL in a local variable.
|
|
62
|
+
This may look dodgy, but should prove reasonably safe in practice.
|
|
63
|
+
*/
|
|
64
|
+
let refUrl = '';
|
|
65
|
+
const captureRefUrl = () => {
|
|
66
|
+
refUrl = document.location.pathname + document.location.search;
|
|
67
|
+
};
|
|
68
|
+
const sendRoutingEvent = (url) => _emitEvent([
|
|
69
|
+
'trackdynamic',
|
|
70
|
+
{
|
|
71
|
+
url,
|
|
72
|
+
ref: refUrl,
|
|
73
|
+
title: document.title,
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
const idToken = '[ACCOUNT_ID]';
|
|
78
|
+
const scriptUrlTemplate = `https://siteimproveanalytics.com/js/siteanalyze_${idToken}.js`;
|
|
79
|
+
/**
|
|
80
|
+
* A component for loading a SiteImprove analytics script and set up page-view
|
|
81
|
+
* tracking across Next.js routes.
|
|
82
|
+
*
|
|
83
|
+
* @see https://github.com/reykjavikcity/webtools/tree/v0.1##siteimprove-component
|
|
84
|
+
*/
|
|
85
|
+
const SiteImprove = (props) => {
|
|
86
|
+
const { analytics } = (0, CookieHubConsent_1.useCookieHubConsent)();
|
|
87
|
+
const consented = (analytics && props.hasConstented !== false) ||
|
|
88
|
+
(analytics === undefined && props.hasConstented);
|
|
89
|
+
(0, react_1.useEffect)(() => {
|
|
90
|
+
if (consented) {
|
|
91
|
+
const routerEvents = router_1.Router.events;
|
|
92
|
+
routerEvents.on('routeChangeStart', captureRefUrl);
|
|
93
|
+
routerEvents.on('routeChangeComplete', sendRoutingEvent);
|
|
94
|
+
return () => {
|
|
95
|
+
routerEvents.off('routeChangeStart', captureRefUrl);
|
|
96
|
+
routerEvents.off('routeChangeComplete', sendRoutingEvent);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}, [consented]);
|
|
100
|
+
if (!consented) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
104
|
+
console.info('Mock loading SiteImprove in development mode.');
|
|
105
|
+
if (!window._sz) {
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
window._sz = window._sz || [];
|
|
108
|
+
}, 300);
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const scriptUrl = props.scriptUrl != null
|
|
113
|
+
? props.scriptUrl
|
|
114
|
+
: scriptUrlTemplate.replace(idToken, props.accountId);
|
|
115
|
+
return (react_1.default.createElement(script_1.default, { type: "text/javascript", strategy: "afterInteractive", src: scriptUrl, onLoad: props.onLoad, onError: props.onError }));
|
|
116
|
+
};
|
|
117
|
+
exports.SiteImprove = SiteImprove;
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
/**
|
|
120
|
+
* A small helper for tracking custom UI events and reporting them to SiteImrove.
|
|
121
|
+
*
|
|
122
|
+
* @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimprove-helper
|
|
123
|
+
*/
|
|
124
|
+
const pingSiteImprove = (category, action, label) => {
|
|
125
|
+
if (process.env.NODE_ENV === 'development' &&
|
|
126
|
+
(!window._sz || window._sz._jit_defined_)) {
|
|
127
|
+
console.warn('`pingSiteImprove` Was called before SiteImprove script was loaded.');
|
|
128
|
+
}
|
|
129
|
+
_emitEvent(['event', category, action, label]);
|
|
130
|
+
};
|
|
131
|
+
exports.pingSiteImprove = pingSiteImprove;
|
package/next/http.d.ts
ADDED
|
@@ -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
|
+
};
|
package/next/http.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
17
|
+
var t = {};
|
|
18
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
19
|
+
t[p] = s[p];
|
|
20
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
21
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
22
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
23
|
+
t[p[i]] = s[p[i]];
|
|
24
|
+
}
|
|
25
|
+
return t;
|
|
26
|
+
};
|
|
27
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
28
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
29
|
+
};
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.notModified304 = exports.makeErrorizeAppHOC = void 0;
|
|
32
|
+
const react_1 = __importDefault(require("react"));
|
|
33
|
+
const http_1 = require("../http");
|
|
34
|
+
/*
|
|
35
|
+
Re-export all of the base [http module](#reykjavikwebtoolshttp)'s exports,
|
|
36
|
+
purely for convenience.
|
|
37
|
+
*/
|
|
38
|
+
__exportStar(require("../http"), exports);
|
|
39
|
+
/**
|
|
40
|
+
* Use this method inside a `getServerSideProps` method (or API route)
|
|
41
|
+
* to return an error page with proper HTTP status code and all the shit.
|
|
42
|
+
*
|
|
43
|
+
* @see https://github.com/reykjavikcity/webtools/tree/v0.1#showerrorpage-helper
|
|
44
|
+
*/
|
|
45
|
+
const showErrorPage = (response, error, ttl = '2s') => {
|
|
46
|
+
error =
|
|
47
|
+
typeof error === 'number'
|
|
48
|
+
? { statusCode: error }
|
|
49
|
+
: error;
|
|
50
|
+
const { statusCode, message } = error, otherProps = __rest(error, ["statusCode", "message"]);
|
|
51
|
+
response = 'res' in response ? response.res : response;
|
|
52
|
+
response.statusCode = error.statusCode;
|
|
53
|
+
(0, http_1.cacheControl)(response, ttl);
|
|
54
|
+
return {
|
|
55
|
+
props: Object.assign(Object.assign({}, otherProps), { __error: { statusCode, message } }),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
// ===========================================================================
|
|
59
|
+
/**
|
|
60
|
+
* Hhigher-order component (HOC) factory for Next.js App compnents to handle
|
|
61
|
+
* cases when `getServerSideProps` returns an `__error` prop with `statusCode`
|
|
62
|
+
* and optional friendly `message`.
|
|
63
|
+
*
|
|
64
|
+
* @see https://github.com/reykjavikcity/webtools/tree/v0.1#makeerrorizeapphoc
|
|
65
|
+
*/
|
|
66
|
+
const makeErrorizeAppHOC = (ErrorPage) => {
|
|
67
|
+
// the HOC
|
|
68
|
+
const withErrorHandling = (App) => {
|
|
69
|
+
const ErrorizedApp = (appProps) => {
|
|
70
|
+
const { pageProps } = appProps;
|
|
71
|
+
if (pageProps.__error) {
|
|
72
|
+
const { __error } = pageProps, otherProps = __rest(pageProps, ["__error"]);
|
|
73
|
+
return (react_1.default.createElement(App, Object.assign({}, appProps, { Component: ErrorPage, pageProps: Object.assign(Object.assign({}, otherProps), __error) })));
|
|
74
|
+
}
|
|
75
|
+
return react_1.default.createElement(App, Object.assign({}, appProps));
|
|
76
|
+
};
|
|
77
|
+
ErrorizedApp.getInitialProps = App.getInitialProps;
|
|
78
|
+
ErrorizedApp.displayName = 'Errorized' + (App.displayName || App.name || 'App');
|
|
79
|
+
return ErrorizedApp;
|
|
80
|
+
};
|
|
81
|
+
withErrorHandling.showErrorPage = showErrorPage;
|
|
82
|
+
return withErrorHandling;
|
|
83
|
+
};
|
|
84
|
+
exports.makeErrorizeAppHOC = makeErrorizeAppHOC;
|
|
85
|
+
// ===========================================================================
|
|
86
|
+
/**
|
|
87
|
+
* Use this method to inside a `getServerSideProps` method (or API route)
|
|
88
|
+
* to return an `HTTP_304_NotModified` response with an empty props object,
|
|
89
|
+
* in a way that doesn't make TypeScript at you.
|
|
90
|
+
*
|
|
91
|
+
* @see https://github.com/reykjavikcity/webtools/tree/v0.1#notmodified304-helper
|
|
92
|
+
*/
|
|
93
|
+
const notModified304 = (response) => {
|
|
94
|
+
response = 'res' in response ? response.res : response;
|
|
95
|
+
response.statusCode = http_1.HTTP_304_NotModified;
|
|
96
|
+
return {
|
|
97
|
+
props:
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
+
{},
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
exports.notModified304 = notModified304;
|
|
103
|
+
// ---------------------------------------------------------------------------
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reykjavik/webtools",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Misc. JS/TS helpers used by Reykjavík City's web dev teams.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": "ssh://git@github.com:reykjavikcity/webtools.git",
|
|
7
|
+
"author": "Reykjavík (http://www.reykjavik.is)",
|
|
8
|
+
"contributors": [
|
|
9
|
+
"Elías Nökkvi Gíslason",
|
|
10
|
+
"Már Örlygsson <mar.orlygsson@reykjavik.is>"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@reykjavik/hanna-utils": "^0.2.3"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"next": ">12",
|
|
18
|
+
"react": ">16.8.0",
|
|
19
|
+
"react-dom": ">16.8.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=16"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"exports": {
|
|
26
|
+
"./CookieHubConsent.tsx": {
|
|
27
|
+
"import": "./esm/CookieHubConsent.tsx.js",
|
|
28
|
+
"require": "./CookieHubConsent.tsx.js"
|
|
29
|
+
},
|
|
30
|
+
"./http": {
|
|
31
|
+
"import": "./esm/http.js",
|
|
32
|
+
"require": "./http.js"
|
|
33
|
+
},
|
|
34
|
+
".": {
|
|
35
|
+
"import": "./esm/index.js",
|
|
36
|
+
"require": "./index.js"
|
|
37
|
+
},
|
|
38
|
+
"./next/http.tsx": {
|
|
39
|
+
"import": "./esm/next/http.tsx.js",
|
|
40
|
+
"require": "./next/http.tsx.js"
|
|
41
|
+
},
|
|
42
|
+
"./next/SiteImprove.tsx": {
|
|
43
|
+
"import": "./esm/next/SiteImprove.tsx.js",
|
|
44
|
+
"require": "./next/SiteImprove.tsx.js"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|