mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-22 11:29:22 -05:00
Add Posthog sync endpoints (#217)
* Add new API endpoints to sync events and persons with Posthog
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query?.environmentId?.toString();
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
// POST
|
||||
if (req.method === "POST") {
|
||||
// lastSyncedAt is the last time the environment was synced (iso string)
|
||||
const { lastSyncedAt } = req.body;
|
||||
|
||||
let lastSyncedCondition = lastSyncedAt
|
||||
? {
|
||||
OR: [
|
||||
{
|
||||
createdAt: {
|
||||
gt: lastSyncedAt,
|
||||
},
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
gt: lastSyncedAt,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {};
|
||||
|
||||
// Get all displays that have been created or updated since lastSyncedAt
|
||||
const displays = await prisma.display.findMany({
|
||||
where: {
|
||||
survey: {
|
||||
environmentId,
|
||||
},
|
||||
...lastSyncedCondition,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
person: {
|
||||
select: {
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Get all responses that have been created or updated since lastSyncedAt
|
||||
const responses = await prisma.response.findMany({
|
||||
where: {
|
||||
survey: {
|
||||
environmentId,
|
||||
},
|
||||
...lastSyncedCondition,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
person: {
|
||||
select: {
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const events = [
|
||||
...displays.map((display) => ({
|
||||
name: "formbricks_display_created",
|
||||
timestamp: display.createdAt,
|
||||
userId: display.person?.attributes?.find((attr) => attr.attributeClass.name === "userId")?.value,
|
||||
})),
|
||||
...responses.map((response) => ({
|
||||
name: "formbricks_response_created",
|
||||
timestamp: response.createdAt,
|
||||
userId: response.person?.attributes?.find((attr) => attr.attributeClass.name === "userId")?.value,
|
||||
})),
|
||||
];
|
||||
|
||||
return res.json({ events, lastSyncedAt: new Date().toISOString() });
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface FormbricksUser {
|
||||
userId: string;
|
||||
attributes: { [key: string]: string };
|
||||
}
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query?.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
// POST
|
||||
if (req.method === "POST") {
|
||||
// lastSyncedAt is the last time the environment was synced (iso string)
|
||||
const { users }: { users: FormbricksUser[] } = req.body;
|
||||
|
||||
for (const user of users) {
|
||||
// check if user with this userId as attribute already exists
|
||||
const existingUser = await prisma.person.findFirst({
|
||||
where: {
|
||||
attributes: {
|
||||
some: {
|
||||
attributeClass: {
|
||||
name: "userId",
|
||||
environmentId,
|
||||
},
|
||||
value: user.userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
const attributeType: "noCode" = "noCode";
|
||||
// create user with this attributes (create or connect attribute with the same attributeClass name)
|
||||
await prisma.person.create({
|
||||
data: {
|
||||
attributes: {
|
||||
create: Object.keys(user.attributes).map((key) => ({
|
||||
value: user.attributes[key],
|
||||
attributeClass: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
name_environmentId: {
|
||||
name: key,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name: key,
|
||||
type: attributeType,
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// user already exists, loop through attributes and update or create them
|
||||
const attributeType: "noCode" = "noCode";
|
||||
for (const key of Object.keys(user.attributes)) {
|
||||
const existingAttribute = existingUser.attributes.find(
|
||||
(attribute) => attribute.attributeClass.name === key
|
||||
);
|
||||
if (existingAttribute) {
|
||||
// skip if value is the same
|
||||
if (existingAttribute.value === user.attributes[key]) {
|
||||
continue;
|
||||
}
|
||||
await prisma.attribute.update({
|
||||
where: {
|
||||
id: existingAttribute.id,
|
||||
},
|
||||
data: {
|
||||
value: user.attributes[key],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// create attribute
|
||||
await prisma.attribute.create({
|
||||
data: {
|
||||
value: user.attributes[key],
|
||||
attributeClass: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
name_environmentId: {
|
||||
name: key,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name: key,
|
||||
type: attributeType,
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
person: {
|
||||
connect: {
|
||||
id: existingUser.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ datasource db {
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["filteredRelationCount"]
|
||||
previewFeatures = ["filteredRelationCount", "extendedWhereUnique"]
|
||||
//provider = "prisma-dbml-generator"
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,8 @@ export const renderWidget = (survey: Survey) => {
|
||||
};
|
||||
|
||||
export const closeSurvey = async (): Promise<void> => {
|
||||
console.log("close survey called");
|
||||
// remove container element from DOM
|
||||
const container = document.getElementById(containerId);
|
||||
container.remove();
|
||||
document.getElementById(containerId).remove();
|
||||
addWidgetContainer();
|
||||
const settings = await getSettings();
|
||||
config.update({ settings });
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["formbricks"],
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
// <TODO: your plugin code here - you can base it on the code below, but you don't have to>
|
||||
|
||||
// Some internal library function
|
||||
async function getRandomNumber() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Plugin method that runs on plugin load
|
||||
export async function setupPlugin({ config }) {
|
||||
console.log(`Setting up the plugin`);
|
||||
}
|
||||
|
||||
// Plugin method that processes event
|
||||
export async function processEvent(event, { config, cache }) {
|
||||
const counterValue = await cache.get("greeting_counter", 0);
|
||||
cache.set("greeting_counter", counterValue + 1);
|
||||
if (!event.properties) event.properties = {};
|
||||
event.properties["greeting"] = config.greeting;
|
||||
event.properties["greeting_counter"] = counterValue;
|
||||
event.properties["random_number"] = await getRandomNumber();
|
||||
return event;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
interface FormbricksUser {
|
||||
userId: string;
|
||||
attributes: { [key: string]: any };
|
||||
}
|
||||
|
||||
export async function setupPlugin({ storage, config, global }) {
|
||||
if (!config.formbricksHost || !config.environmentId || config.apiKey) {
|
||||
throw new Error("Please set the 'formbricksHost', 'environmentId' & 'apiKey' config values");
|
||||
}
|
||||
|
||||
const resetStorage = config.resetStorage === "Yes";
|
||||
|
||||
if (resetStorage) {
|
||||
await storage.del("formbricks-lastSyncedAt");
|
||||
}
|
||||
|
||||
if (!global.projectId) {
|
||||
throw new Error(`Could not get ID for Github project: ${config.user}/${config.repo}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runEveryHour({ cache, storage, global, config }) {
|
||||
let lastSyncedAt = await storage.get("formbricks-lastSyncedAt", null);
|
||||
if (config.import === "Yes") {
|
||||
const response = await fetch(
|
||||
`${config.formbricksHost}/api/v1/environemnts/${config.environmentId}/posthog/export`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": config.apiKey,
|
||||
},
|
||||
body: JSON.stringify({ lastSyncedAt }),
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
for (const event of result.events) {
|
||||
posthog.capture(event.name, {
|
||||
timestamp: event.timestamp,
|
||||
userId: event.userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (config.export === "Yes") {
|
||||
const userRes = await posthog.api.get("/api/projects/@current/persons", {
|
||||
host: global.posthogUrl,
|
||||
personalApiKey: global.posthogApiKey,
|
||||
projectApiKey: global.posthogProjectKey,
|
||||
});
|
||||
const userResponse = await userRes.json();
|
||||
|
||||
const users: FormbricksUser[] = [];
|
||||
|
||||
if (userResponse.results && userResponse.results.length > 0) {
|
||||
for (const loadedUser of userResponse["results"]) {
|
||||
for (const distinctId of loadedUser["distinct_ids"]) {
|
||||
users.push({
|
||||
userId: distinctId,
|
||||
attributes: loadedUser["properties"],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await fetch(`${config.formbricksHost}/api/v1/environemnts/${config.environmentId}/posthog/import`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": config.apiKey,
|
||||
},
|
||||
body: JSON.stringify({ users }),
|
||||
});
|
||||
}
|
||||
await storage.set("formbricks-lastSyncedAt", new Date().toISOString());
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@formbricks/posthog-formbricks-plugin",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
|
||||
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
|
||||
"lint:report": "eslint . --format json --output-file ../../lint-results/app-store.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "*",
|
||||
"@formbricks/types": "*",
|
||||
"@posthog/plugin-scaffold": "^1.4.2",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,51 @@
|
||||
"name": "Formbricks",
|
||||
"config": [
|
||||
{
|
||||
"markdown": "## MANUAL STEP NOTICE: This app needs to injects code into your website through posthog-js. You need to **opt-in** on your site to enable this behaviour.\n\n```\nposthog.init(\"api_key\", {\n \"api_host\": \"https://app.posthog.com\",\n \"opt_in_site_apps\": true,\n})\n```"
|
||||
"key": "formbricksHost",
|
||||
"name": "Formbricks Host",
|
||||
"type": "string",
|
||||
"default": "https://app.formbricks.com",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "environmentId",
|
||||
"name": "Environment ID",
|
||||
"type": "string",
|
||||
"hint": "Can be found in the Setup Checklist in the Formbricks Settings",
|
||||
"default": "clfwl2u460003qo0hnmw4lihk",
|
||||
"required": true,
|
||||
"site": true
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "formbricksHost",
|
||||
"name": "Formbricks Host",
|
||||
"key": "formbricksApiKey",
|
||||
"name": "Formbricks API Key",
|
||||
"type": "string",
|
||||
"default": "https://app.formbricks.com",
|
||||
"required": true,
|
||||
"site": true
|
||||
"secret": true,
|
||||
"hint": "You can generate a new API Key in the Formbricks Settings",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "import",
|
||||
"name": "Enable Import",
|
||||
"type": "choice",
|
||||
"hint": "Do you want to import display events and responses from Formbricks?",
|
||||
"default": "Yes",
|
||||
"choices": ["Yes", "No"]
|
||||
},
|
||||
{
|
||||
"key": "export",
|
||||
"name": "Enable Export",
|
||||
"type": "choice",
|
||||
"hint": "Do you want to send Posthog users and their attributes to Formbricks?",
|
||||
"default": "Yes",
|
||||
"choices": ["Yes", "No"]
|
||||
},
|
||||
{
|
||||
"key": "resetStorage",
|
||||
"name": "Reset",
|
||||
"type": "choice",
|
||||
"choices": ["No", "Yes"],
|
||||
"hint": "**Advanced** - Reset the plugin's storage. This will catch up all events from the beginning of time.",
|
||||
"required": false,
|
||||
"default": "No"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
export function inject({ config, posthog }) {
|
||||
const shadow = createShadow();
|
||||
const formbricksScript = document.createElement("script");
|
||||
formbricksScript.type = "text/javascript";
|
||||
formbricksScript.async = true;
|
||||
formbricksScript.src = "https://unpkg.com/@formbricks/js@^0.1.4/dist/index.umd.js";
|
||||
|
||||
shadow.appendChild(formbricksScript);
|
||||
|
||||
formbricksScript.onload = () => {
|
||||
console.log("initializing");
|
||||
setTimeout(() => {
|
||||
window.formbricks = window.js;
|
||||
window.formbricks.init({
|
||||
environmentId: config.environmentId,
|
||||
apiHost: config.formbricksHost,
|
||||
logLevel: "debug",
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
}
|
||||
|
||||
function createShadow(): ShadowRoot {
|
||||
const div = document.createElement("div");
|
||||
const shadow = div.attachShadow({ mode: "open" });
|
||||
document.body.appendChild(div);
|
||||
return shadow;
|
||||
}
|
||||
Generated
+85
@@ -491,6 +491,27 @@ importers:
|
||||
specifier: ^4.9.4
|
||||
version: 4.9.5
|
||||
|
||||
packages/posthog-formbricks-plugin:
|
||||
devDependencies:
|
||||
'@formbricks/tsconfig':
|
||||
specifier: '*'
|
||||
version: link:../tsconfig
|
||||
'@formbricks/types':
|
||||
specifier: '*'
|
||||
version: link:../types
|
||||
'@posthog/plugin-scaffold':
|
||||
specifier: ^1.4.2
|
||||
version: 1.4.2
|
||||
eslint:
|
||||
specifier: ^8.27.0
|
||||
version: 8.37.0
|
||||
eslint-config-formbricks:
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-config-formbricks
|
||||
typescript:
|
||||
specifier: ^4.9.4
|
||||
version: 4.9.5
|
||||
|
||||
packages/prettier-config:
|
||||
devDependencies:
|
||||
prettier:
|
||||
@@ -3034,6 +3055,14 @@ packages:
|
||||
unist-util-visit: 2.0.3
|
||||
dev: false
|
||||
|
||||
/@maxmind/geoip2-node@3.5.0:
|
||||
resolution: {integrity: sha512-WG2TNxMwDWDOrljLwyZf5bwiEYubaHuICvQRlgz74lE9OZA/z4o+ZT6OisjDBAZh/yRJVNK6mfHqmP5lLlAwsA==}
|
||||
dependencies:
|
||||
camelcase-keys: 7.0.2
|
||||
ip6addr: 0.2.5
|
||||
maxmind: 4.3.10
|
||||
dev: true
|
||||
|
||||
/@mdn/browser-compat-data@3.3.14:
|
||||
resolution: {integrity: sha512-n2RC9d6XatVbWFdHLimzzUJxJ1KY8LdjqrW6YvGPiRmsHkhOUx74/Ct10x5Yo7bC/Jvqx7cDEW8IMPv/+vwEzA==}
|
||||
dev: true
|
||||
@@ -3344,6 +3373,12 @@ packages:
|
||||
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
|
||||
dev: true
|
||||
|
||||
/@posthog/plugin-scaffold@1.4.2:
|
||||
resolution: {integrity: sha512-/VsRg3CfhQvYhxM2O9+gBOzj4K1QJZClY+yple0npL1Jd2nRn2nT4z7dlPSidTPZvdpFs0+hrnF+m4Kxf1NFvQ==}
|
||||
dependencies:
|
||||
'@maxmind/geoip2-node': 3.5.0
|
||||
dev: true
|
||||
|
||||
/@preact/async-loader@3.0.2(preact@10.13.2):
|
||||
resolution: {integrity: sha512-nYIdlAGbZ0+0/u5VJxQdLDgNFgEJmNLzctuqnCBZxW/EpLPMg8lcsnRsoXVl+O28ZZYhVhos3XiWM3KtuN0C3Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6425,6 +6460,16 @@ packages:
|
||||
quick-lru: 4.0.1
|
||||
dev: true
|
||||
|
||||
/camelcase-keys@7.0.2:
|
||||
resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
camelcase: 6.3.0
|
||||
map-obj: 4.3.0
|
||||
quick-lru: 5.1.1
|
||||
type-fest: 1.4.0
|
||||
dev: true
|
||||
|
||||
/camelcase@4.1.0:
|
||||
resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -10488,6 +10533,13 @@ packages:
|
||||
loose-envify: 1.4.0
|
||||
dev: false
|
||||
|
||||
/ip6addr@0.2.5:
|
||||
resolution: {integrity: sha512-9RGGSB6Zc9Ox5DpDGFnJdIeF0AsqXzdH+FspCfPPaU/L/4tI6P+5lIoFUFm9JXs9IrJv1boqAaNCQmoDADTSKQ==}
|
||||
dependencies:
|
||||
assert-plus: 1.0.0
|
||||
jsprim: 2.0.2
|
||||
dev: true
|
||||
|
||||
/ip@1.1.8:
|
||||
resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==}
|
||||
dev: true
|
||||
@@ -11757,6 +11809,16 @@ packages:
|
||||
verror: 1.10.0
|
||||
dev: true
|
||||
|
||||
/jsprim@2.0.2:
|
||||
resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==}
|
||||
engines: {'0': node >=0.6.0}
|
||||
dependencies:
|
||||
assert-plus: 1.0.0
|
||||
extsprintf: 1.3.0
|
||||
json-schema: 0.4.0
|
||||
verror: 1.10.0
|
||||
dev: true
|
||||
|
||||
/jsx-ast-utils@3.3.3:
|
||||
resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==}
|
||||
engines: {node: '>=4.0'}
|
||||
@@ -12192,6 +12254,14 @@ packages:
|
||||
pretty-bytes: 3.0.1
|
||||
dev: true
|
||||
|
||||
/maxmind@4.3.10:
|
||||
resolution: {integrity: sha512-H83pPwi4OqpjPmvAVtuimVWFe6JwHdFK+UIzq4KdvQrKUMLieIrsvU/A9N8jbmOqC2JJPA+jtlFwodyqmzl/3w==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
dependencies:
|
||||
mmdb-lib: 2.0.2
|
||||
tiny-lru: 10.4.1
|
||||
dev: true
|
||||
|
||||
/md5.js@1.3.5:
|
||||
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
|
||||
dependencies:
|
||||
@@ -13060,6 +13130,11 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/mmdb-lib@2.0.2:
|
||||
resolution: {integrity: sha512-shi1I+fCPQonhTi7qyb6hr7hi87R7YS69FlfJiMFuJ12+grx0JyL56gLNzGTYXPU7EhAPkMLliGeyHer0K+AVA==}
|
||||
engines: {node: '>=10', npm: '>=6'}
|
||||
dev: true
|
||||
|
||||
/moo@0.5.2:
|
||||
resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==}
|
||||
dev: true
|
||||
@@ -17617,6 +17692,11 @@ packages:
|
||||
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
|
||||
dev: false
|
||||
|
||||
/tiny-lru@10.4.1:
|
||||
resolution: {integrity: sha512-buLIzw7ppqymuO3pt10jHk/6QMeZLbidihMQU+N6sogF6EnBzG0qtDWIHuhw1x3dyNgVL/KTGIZsTK81+yCzLg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/tmp@0.0.33:
|
||||
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
||||
engines: {node: '>=0.6.0'}
|
||||
@@ -17969,6 +18049,11 @@ packages:
|
||||
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/type-fest@1.4.0:
|
||||
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
Reference in New Issue
Block a user