forked from cms/tibi-svelte-starter
yarn upgrade
This commit is contained in:
74
api/hooks/lib/ssr-server.js
Normal file
74
api/hooks/lib/ssr-server.js
Normal file
@@ -0,0 +1,74 @@
|
||||
const { apiSsrBaseURL, ssrPublishCheckCollections } = require("../config")
|
||||
|
||||
/**
|
||||
* api request via server, cache result in context.ssrCache
|
||||
* should be elimated in client code via tree shaking
|
||||
*
|
||||
* @param {string} cacheKey
|
||||
* @param {string} endpoint
|
||||
* @param {string} query
|
||||
* @param {ApiOptions} options
|
||||
* @returns {ApiResult<any>}
|
||||
*/
|
||||
function ssrRequest(cacheKey, endpoint, query, options) {
|
||||
let url = endpoint + (query ? "?" + query : "")
|
||||
|
||||
if (ssrPublishCheckCollections?.includes(endpoint)) {
|
||||
// @ts-ignore
|
||||
let validUntil = context.ssrCacheValidUntil
|
||||
|
||||
// check in db for publish date to invalidate cache
|
||||
const _optionsPublishSearch = Object.assign(
|
||||
{},
|
||||
{ filter: options?.filter },
|
||||
{
|
||||
selector: { publication: 1 },
|
||||
projection: null,
|
||||
}
|
||||
)
|
||||
const publishSearch = context.db.find(endpoint, _optionsPublishSearch)
|
||||
publishSearch?.forEach((item) => {
|
||||
const publicationFrom = item.publication?.from ? new Date(item.publication.from.unixMilli()) : null
|
||||
const publicationTo = item.publication?.to ? new Date(item.publication.to.unixMilli()) : null
|
||||
|
||||
if (publicationFrom && publicationFrom > new Date()) {
|
||||
// entry has a publish date that is further in in the future than current, set global validUntil
|
||||
if (validUntil == null || validUntil > publicationFrom) {
|
||||
validUntil = publicationFrom
|
||||
}
|
||||
}
|
||||
if (publicationTo && publicationTo > new Date()) {
|
||||
// entry has a unpublish date that is further in in the future than current, set global validUntil
|
||||
if (validUntil == null || validUntil > publicationTo) {
|
||||
validUntil = publicationTo
|
||||
}
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
context.ssrCacheValidUntil = validUntil
|
||||
}
|
||||
|
||||
// console.log("############ FETCHING ", apiSsrBaseURL + url)
|
||||
|
||||
const response = context.http.fetch(apiSsrBaseURL + url, {
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
})
|
||||
|
||||
// console.log(JSON.stringify(response.headers, null, 2))
|
||||
|
||||
const json = response.body.json()
|
||||
const count = parseInt(response.headers["X-Results-Count"] || "0")
|
||||
|
||||
// json is go data structure and incompatible with js, so we need to convert it
|
||||
const r = { data: JSON.parse(JSON.stringify(json)), count: count }
|
||||
|
||||
// @ts-ignore
|
||||
context.ssrCache[cacheKey] = r
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ssrRequest,
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
const { apiClientBaseURL } = require("../config-client")
|
||||
|
||||
/**
|
||||
* convert object to string
|
||||
* @param {any} obj object
|
||||
* @returns {string} string
|
||||
*/
|
||||
function obj2str(obj) {
|
||||
if (Array.isArray(obj)) {
|
||||
@@ -30,8 +33,105 @@ function obj2str(obj) {
|
||||
if (obj) return obj
|
||||
}
|
||||
|
||||
// can be used by client code, so DONT INCLUDE hooks/config.js (SECRETS INSIDE)
|
||||
/**
|
||||
* api request via client or server
|
||||
* server function ssrRequest is called via context.ssrRequest, binded in ssr hook
|
||||
*
|
||||
* @param {string} endpoint
|
||||
* @param {ApiOptions} options
|
||||
* @param {any} body
|
||||
* @param {import("../../../frontend/src/sentry")} sentry
|
||||
* @param {typeof fetch} _fetch
|
||||
* @returns {Promise<ApiResult<any>>}
|
||||
*/
|
||||
function apiRequest(endpoint, options, body, sentry, _fetch) {
|
||||
// TODO cache only for GET
|
||||
|
||||
// first check cache if on client
|
||||
const cacheKey = obj2str({ endpoint: endpoint, options: options })
|
||||
|
||||
let method = options?.method || "GET"
|
||||
|
||||
// @ts-ignore
|
||||
if (typeof window !== "undefined" && window.__SSR_CACHE__ && method === "GET") {
|
||||
// @ts-ignore
|
||||
const cache = window.__SSR_CACHE__[cacheKey]
|
||||
console.log("SSR HIT:", cacheKey, cache)
|
||||
if (cache) {
|
||||
return Promise.resolve(cache)
|
||||
}
|
||||
}
|
||||
|
||||
let query = "&count=1"
|
||||
if (options?.filter) query += "&filter=" + encodeURIComponent(JSON.stringify(options.filter))
|
||||
if (options?.sort) query += "&sort=" + options.sort + "&sort=_id"
|
||||
if (options?.limit) query += "&limit=" + options.limit
|
||||
if (options?.offset) query += "&offset=" + options.offset
|
||||
if (options?.projection) query += "&projection=" + options.projection
|
||||
if (options?.lookup) query += "&lookup=" + options.lookup
|
||||
|
||||
if (options?.params) {
|
||||
Object.keys(options.params).forEach((p) => {
|
||||
query += "&" + p + "=" + encodeURIComponent(options.params[p])
|
||||
})
|
||||
}
|
||||
|
||||
/** @type {{[key: string]: string}} */
|
||||
let headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if (options?.headers) headers = { ...headers, ...options.headers }
|
||||
|
||||
if (typeof window === "undefined" && method === "GET") {
|
||||
// server
|
||||
|
||||
// reference via context from get hook to tree shake in client
|
||||
// @ts-ignore
|
||||
const d = context.ssrRequest(cacheKey, endpoint, query, Object.assign({}, options, { method, headers }))
|
||||
return d
|
||||
} else {
|
||||
// client
|
||||
let url = (endpoint.startsWith("/") ? "" : apiClientBaseURL) + endpoint + (query ? "?" + query : "")
|
||||
|
||||
const span = sentry?.currentTransaction()?.startChild({
|
||||
op: "fetch",
|
||||
description: method + " " + url,
|
||||
data: Object.assign({}, options, { url }),
|
||||
})
|
||||
const trace_id = span?.toTraceparent()
|
||||
if (trace_id) {
|
||||
headers["sentry-trace"] = trace_id
|
||||
}
|
||||
|
||||
/** @type {{[key: string]: any}} */
|
||||
const requestOptions = {
|
||||
method,
|
||||
mode: "cors",
|
||||
headers,
|
||||
}
|
||||
|
||||
if (method === "POST" || method === "PUT") {
|
||||
requestOptions.body = JSON.stringify(body)
|
||||
}
|
||||
|
||||
const response = _fetch(url, requestOptions).then((response) => {
|
||||
return response?.json().then((json) => {
|
||||
if (response?.status < 200 || response?.status >= 400) {
|
||||
return Promise.reject({ response, data: json })
|
||||
}
|
||||
return Promise.resolve({ data: json || null, count: response.headers?.get("x-results-count") || 0 })
|
||||
})
|
||||
})
|
||||
|
||||
span?.end()
|
||||
|
||||
// @ts-ignore
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
obj2str,
|
||||
apiRequest,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// @ts-check
|
||||
|
||||
var config = require("../config")
|
||||
const { cryptchaSiteId } = require("../config-client")
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -9,208 +7,83 @@ var config = require("../config")
|
||||
function log(str) {
|
||||
console.log(JSON.stringify(str, undefined, 4))
|
||||
}
|
||||
function rand() {
|
||||
return Math.random().toString(36).substr(2) // remove `0.`
|
||||
}
|
||||
|
||||
/**
|
||||
* convert object to string
|
||||
* @param {any} obj object
|
||||
* @returns {string}
|
||||
*/
|
||||
function randomToken() {
|
||||
return rand() + rand()
|
||||
}
|
||||
function obj2str(obj) {
|
||||
if (Array.isArray(obj)) {
|
||||
return JSON.stringify(
|
||||
obj.map(function (idx) {
|
||||
return obj2str(idx)
|
||||
})
|
||||
)
|
||||
} else if (typeof obj === "object" && obj !== null) {
|
||||
var elements = Object.keys(obj)
|
||||
.sort()
|
||||
.map(function (key) {
|
||||
var val = obj2str(obj[key])
|
||||
if (val) {
|
||||
return key + ":" + val
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('tibi-types').HookContext} c
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isPublicToken(c) {
|
||||
var r = c.request()
|
||||
|
||||
var t = r.query("token") || r.header("token")
|
||||
return t == config.publicToken
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('tibi-types').HookContext} c
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isSsrToken(c) {
|
||||
var r = c.request()
|
||||
|
||||
var t = r.query("token") || r.header("token")
|
||||
return t == config.ssrToken
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('tibi-types').HookContext} c
|
||||
* @param {string} filename
|
||||
* @returns {string}
|
||||
*/
|
||||
function tpl(c, filename) {
|
||||
return c.tpl.execute(c.fs.readFile(filename), {
|
||||
context: c,
|
||||
config: config,
|
||||
/**
|
||||
* @param {number} v
|
||||
* @param {number} vat
|
||||
*/
|
||||
formatPrice: function (v, vat) {
|
||||
if (vat) {
|
||||
v *= vat / 100 + 1
|
||||
}
|
||||
return v.toFixed(2).toString().replace(".", ",")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Base64 encode / decode
|
||||
* http://www.webtoolkit.info/
|
||||
*
|
||||
**/
|
||||
var Base64 = {
|
||||
// private property
|
||||
_keyStr:
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
|
||||
// public method for encoding
|
||||
encode: function (input) {
|
||||
var output = ""
|
||||
var chr1, chr2, chr3, enc1, enc2, enc3, enc4
|
||||
var i = 0
|
||||
input = Base64._utf8_encode(input)
|
||||
while (i < input.length) {
|
||||
chr1 = input.charCodeAt(i++)
|
||||
chr2 = input.charCodeAt(i++)
|
||||
chr3 = input.charCodeAt(i++)
|
||||
enc1 = chr1 >> 2
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)
|
||||
enc4 = chr3 & 63
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64
|
||||
}
|
||||
output =
|
||||
output +
|
||||
this._keyStr.charAt(enc1) +
|
||||
this._keyStr.charAt(enc2) +
|
||||
this._keyStr.charAt(enc3) +
|
||||
this._keyStr.charAt(enc4)
|
||||
var elementsCleaned = []
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (elements[i]) elementsCleaned.push(elements[i])
|
||||
}
|
||||
return output
|
||||
},
|
||||
// public method for decoding
|
||||
decode: function (input) {
|
||||
var output = ""
|
||||
var chr1, chr2, chr3
|
||||
var enc1, enc2, enc3, enc4
|
||||
var i = 0
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "")
|
||||
while (i < input.length) {
|
||||
enc1 = this._keyStr.indexOf(input.charAt(i++))
|
||||
enc2 = this._keyStr.indexOf(input.charAt(i++))
|
||||
enc3 = this._keyStr.indexOf(input.charAt(i++))
|
||||
enc4 = this._keyStr.indexOf(input.charAt(i++))
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4)
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2)
|
||||
chr3 = ((enc3 & 3) << 6) | enc4
|
||||
output = output + String.fromCharCode(chr1)
|
||||
if (enc3 != 64) {
|
||||
output = output + String.fromCharCode(chr2)
|
||||
}
|
||||
if (enc4 != 64) {
|
||||
output = output + String.fromCharCode(chr3)
|
||||
}
|
||||
}
|
||||
output = Base64._utf8_decode(output)
|
||||
return output
|
||||
},
|
||||
// private method for UTF-8 encoding
|
||||
_utf8_encode: function (string) {
|
||||
string = string.replace(/\r\n/g, "\n")
|
||||
var utftext = ""
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
var c = string.charCodeAt(n)
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c)
|
||||
} else if (c > 127 && c < 2048) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192)
|
||||
utftext += String.fromCharCode((c & 63) | 128)
|
||||
} else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224)
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128)
|
||||
utftext += String.fromCharCode((c & 63) | 128)
|
||||
}
|
||||
}
|
||||
return utftext
|
||||
},
|
||||
// private method for UTF-8 decoding
|
||||
_utf8_decode: function (utftext) {
|
||||
var string = ""
|
||||
var i = 0
|
||||
var c = 0
|
||||
var c1 = 0
|
||||
var c2 = 0
|
||||
var c3 = 0
|
||||
while (i < utftext.length) {
|
||||
c = utftext.charCodeAt(i)
|
||||
if (c < 128) {
|
||||
string += String.fromCharCode(c)
|
||||
i++
|
||||
} else if (c > 191 && c < 224) {
|
||||
c2 = utftext.charCodeAt(i + 1)
|
||||
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63))
|
||||
i += 2
|
||||
} else {
|
||||
c2 = utftext.charCodeAt(i + 1)
|
||||
c3 = utftext.charCodeAt(i + 2)
|
||||
string += String.fromCharCode(
|
||||
((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)
|
||||
)
|
||||
i += 3
|
||||
}
|
||||
}
|
||||
return string
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} d
|
||||
*/
|
||||
function parseDate(d) {
|
||||
return new Date(
|
||||
d
|
||||
.toString()
|
||||
// go time objects output is not parseable by goja, so fix it
|
||||
.replace(
|
||||
/[T ](\d{2}:\d{2}:\d{2})(?:\.\d+)? ([\+\-])(\d{4}).*$/,
|
||||
"T$1$2$3"
|
||||
)
|
||||
)
|
||||
return "{" + elementsCleaned.join("|") + "}"
|
||||
}
|
||||
|
||||
if (obj) return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* clear SSR cache
|
||||
* @returns {number}
|
||||
*/
|
||||
function clearSSRCache() {
|
||||
var info = context.db.deleteMany("ssr", {})
|
||||
context.response.header("X-SSR-Cleared", info.removed)
|
||||
return info.removed
|
||||
}
|
||||
|
||||
function cryptchaCheck() {
|
||||
const solutionId = context.data._sId
|
||||
const solution = context.data._s
|
||||
|
||||
const body = JSON.stringify({ solution: solution })
|
||||
console.log(body)
|
||||
const resp = context.http.fetch(
|
||||
"https://cryptcha.webmakers.de/api/command?siteId=" +
|
||||
cryptchaSiteId +
|
||||
"&cmd=check&clear=1&solutionId=" +
|
||||
solutionId,
|
||||
{
|
||||
method: "POST",
|
||||
body,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
)
|
||||
const j = resp.body.json()
|
||||
|
||||
const corrent = j.status == "ok" && resp.status == 200
|
||||
if (!corrent) {
|
||||
throw {
|
||||
status: 400,
|
||||
log: false,
|
||||
error: "incorrect data",
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
randomToken,
|
||||
isPublicToken,
|
||||
isSsrToken,
|
||||
tpl: tpl,
|
||||
Base64,
|
||||
parseDate,
|
||||
clearSSRCache,
|
||||
ssrValidatePath: config.ssRValidatePath,
|
||||
obj2str,
|
||||
cryptchaCheck,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user