Compare commits

...

8 Commits

Author SHA1 Message Date
ea93c1f508 fix: asdf 2024-04-26 18:31:49 +07:00
2dd3e4440f fix: yeet 2024-04-26 18:11:15 +07:00
20edee7f1a fix: ssr feature flags (#1119)
## Description

Feature flags are broken on SSR due to this error

```
TypeError: fetch failed
    at Object.fetch (node:internal/deps/undici/undici:11731:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  cause: RequestContentLengthMismatchError: Request body length does not match content-length header
      at write (node:internal/deps/undici/undici:8590:41)
      at _resume (node:internal/deps/undici/undici:8563:33)
      at resume (node:internal/deps/undici/undici:8459:7)
      at [dispatch] (node:internal/deps/undici/undici:7704:11)
      at Client.Intercept (node:internal/deps/undici/undici:7377:20)
      at Client.dispatch (node:internal/deps/undici/undici:6023:44)
      at [dispatch] (node:internal/deps/undici/undici:6254:32)
      at Pool.dispatch (node:internal/deps/undici/undici:6023:44)
      at [dispatch] (node:internal/deps/undici/undici:9343:27)
      at Agent.Intercept (node:internal/deps/undici/undici:7377:20) {
    code: 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
  }
}
```

I've removed content-length header since it isn't mandatory to my
knowledge for get requests.

## Changes

- Add fallback local flags when individual flag request fails
- Add error logging
- Remove `content-length` from headers being passed to Posthog
2024-04-26 16:01:09 +07:00
9bc5818d19 fix: use cdp and upgrade playwright again (#1117)
## Description

Upgrade playwright once again and use CDP for remote connections to
avoid version lock-in with `playwright.connect`

Resolves the issue where all CI is failing currently due to downgrading
playwright.
2024-04-26 15:56:55 +10:00
481d739c37 chore: update package-lock 2024-04-26 13:25:16 +10:00
88dedc9829 fix: use cdp and upgrade playwright again 2024-04-26 13:18:31 +10:00
e949fb14ae fix: hide team webhooks from users (#1116)
## Description

Currently if you create a team webhook, you can see it in your personal
webhooks.

However, interacting with them will throw errors because there is server
side logic preventing any interaction with them since they are outside
of the correct context.

So the solution is to either:
- Allow the user to edit the team webhooks that they created on their
personal webhooks page
- Hide team webhooks for personal webhooks pages

This PR goes with the second option, but is open to suggestions.
2024-04-26 11:14:52 +10:00
e1573465f6 fix: hide team webhooks from users 2024-04-25 23:32:59 +07:00
8 changed files with 178 additions and 68 deletions

96
package-lock.json generated
View File

@ -22,7 +22,7 @@
"eslint-config-custom": "*",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"playwright": "1.41.0",
"playwright": "1.43.0",
"prettier": "^2.5.1",
"rimraf": "^5.0.1",
"turbo": "^1.9.3"
@ -4702,13 +4702,26 @@
"node": ">=14"
}
},
"node_modules/@playwright/browser-chromium": {
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.43.0.tgz",
"integrity": "sha512-F0S4KIqSqQqm9EgsdtWjaJRpgP8cD2vWZHPSB41YI00PtXUobiv/3AnYISeL7wNuTanND7giaXQ4SIjkcIq3KQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"playwright-core": "1.43.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@playwright/test": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.0.tgz",
"integrity": "sha512-PdW+kn4eV99iP5gxWNSDQCbhMaDVej+RXL5xr6t04nbKLCBwYtA046t7ofoczHOm8u6c+45hpDKQVZqtqwkeQg==",
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz",
"integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==",
"dev": true,
"dependencies": {
"playwright": "1.40.0"
"playwright": "1.43.1"
},
"bin": {
"playwright": "cli.js"
@ -4732,12 +4745,12 @@
}
},
"node_modules/@playwright/test/node_modules/playwright": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.0.tgz",
"integrity": "sha512-gyHAgQjiDf1m34Xpwzaqb76KgfzYrhK7iih+2IzcOCoZWr/8ZqmdBw+t0RU85ZmfJMgtgAiNtBQ/KS2325INXw==",
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz",
"integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==",
"dev": true,
"dependencies": {
"playwright-core": "1.40.0"
"playwright-core": "1.43.1"
},
"bin": {
"playwright": "cli.js"
@ -4750,9 +4763,9 @@
}
},
"node_modules/@playwright/test/node_modules/playwright-core": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz",
"integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==",
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz",
"integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@ -17660,11 +17673,11 @@
}
},
"node_modules/playwright": {
"version": "1.41.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.0.tgz",
"integrity": "sha512-XOsfl5ZtAik/T9oek4V0jAypNlaCNzuKOwVhqhgYT3os6kH34PzbRb74F0VWcLYa5WFdnmxl7qyAHBXvPv7lqQ==",
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.0.tgz",
"integrity": "sha512-SiOKHbVjTSf6wHuGCbqrEyzlm6qvXcv7mENP+OZon1I07brfZLGdfWV0l/efAzVx7TF3Z45ov1gPEkku9q25YQ==",
"dependencies": {
"playwright-core": "1.41.0"
"playwright-core": "1.43.0"
},
"bin": {
"playwright": "cli.js"
@ -17676,6 +17689,17 @@
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.0.tgz",
"integrity": "sha512-iWFjyBUH97+pUFiyTqSLd8cDMMOS0r2ZYz2qEsPjH8/bX++sbIJT35MSwKnp1r/OQBAqC5XO99xFbJ9XClhf4w==",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@ -17689,17 +17713,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright/node_modules/playwright-core": {
"version": "1.41.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.0.tgz",
"integrity": "sha512-UGKASUhXmvqm2Lxa1fNr8sFwAtqjpgBRr9jQ7XBI8Rn5uFiEowGUGwrruUQsVPIom4bk7Lt+oLGpXobnXzrBIw==",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
@ -24968,7 +24981,7 @@
"next-auth": "4.24.5",
"oslo": "^0.17.0",
"pdf-lib": "^1.17.1",
"playwright": "1.41.0",
"playwright": "1.43.0",
"react": "18.2.0",
"remeda": "^1.27.1",
"stripe": "^12.7.0",
@ -24976,23 +24989,10 @@
"zod": "^3.22.4"
},
"devDependencies": {
"@playwright/browser-chromium": "1.41.0",
"@playwright/browser-chromium": "1.43.0",
"@types/luxon": "^3.3.1"
}
},
"packages/lib/node_modules/@playwright/browser-chromium": {
"version": "1.41.0",
"resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.41.0.tgz",
"integrity": "sha512-TaHfh3rDsz4+tVKdMMo4kdFOk8/4U6cPyMXHhoiJVmhOhjHXjR0qPMoa5gz5jDGl478cn5SoXmtgKPgTDFuS0g==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"playwright-core": "1.41.0"
},
"engines": {
"node": ">=16"
}
},
"packages/lib/node_modules/nanoid": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
@ -25010,18 +25010,6 @@
"node": "^14 || ^16 || >=18"
}
},
"packages/lib/node_modules/playwright-core": {
"version": "1.41.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.0.tgz",
"integrity": "sha512-UGKASUhXmvqm2Lxa1fNr8sFwAtqjpgBRr9jQ7XBI8Rn5uFiEowGUGwrruUQsVPIom4bk7Lt+oLGpXobnXzrBIw==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"packages/prettier-config": {
"name": "@documenso/prettier-config",
"version": "0.0.0",

View File

@ -38,7 +38,7 @@
"eslint-config-custom": "*",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"playwright": "1.41.0",
"playwright": "1.43.0",
"prettier": "^2.5.1",
"rimraf": "^5.0.1",
"turbo": "^1.9.3"

View File

@ -39,7 +39,7 @@
"next-auth": "4.24.5",
"oslo": "^0.17.0",
"pdf-lib": "^1.17.1",
"playwright": "1.41.0",
"playwright": "1.43.0",
"react": "18.2.0",
"remeda": "^1.27.1",
"stripe": "^12.7.0",
@ -48,6 +48,6 @@
},
"devDependencies": {
"@types/luxon": "^3.3.1",
"@playwright/browser-chromium": "1.41.0"
"@playwright/browser-chromium": "1.43.0"
}
}

View File

@ -47,7 +47,13 @@ export const completeDocumentWithToken = async ({
}: CompleteDocumentWithTokenOptions) => {
'use server';
const startTime = Date.now();
console.log('Start:' + startTime);
console.log('getDocumentStart:' + startTime);
const document = await getDocument({ token, documentId });
console.log('getDocumentEnd:' + (Date.now() - startTime));
console.log('Acc:' + (Date.now() - startTime));
if (document.status !== DocumentStatus.PENDING) {
throw new Error(`Document ${document.id} must be pending`);
@ -63,12 +69,16 @@ export const completeDocumentWithToken = async ({
throw new Error(`Recipient ${recipient.id} has already signed`);
}
const fieldStartTime = Date.now();
console.log('fieldStart:' + fieldStartTime);
const fields = await prisma.field.findMany({
where: {
documentId: document.id,
recipientId: recipient.id,
},
});
console.log('fieldEnd:' + (Date.now() - fieldStartTime));
console.log('Acc:' + (Date.now() - startTime));
if (fields.some((field) => !field.inserted)) {
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
@ -93,6 +103,9 @@ export const completeDocumentWithToken = async ({
// throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
// }
const recipientUpdateStartTime = Date.now();
console.log('recipientUpdateStart:' + recipientUpdateStartTime);
await prisma.$transaction(async (tx) => {
await tx.recipient.update({
where: {
@ -124,6 +137,12 @@ export const completeDocumentWithToken = async ({
});
});
console.log('recipientUpdateEnd:' + (Date.now() - recipientUpdateStartTime));
console.log('Acc:' + (Date.now() - startTime));
const pendingRecipientsStartTime = Date.now();
console.log('pendingRecipientsStart:' + pendingRecipientsStartTime);
const pendingRecipients = await prisma.recipient.count({
where: {
documentId: document.id,
@ -132,11 +151,16 @@ export const completeDocumentWithToken = async ({
},
},
});
console.log('pendingRecipientsEnd:' + (Date.now() - pendingRecipientsStartTime));
console.log('Acc:' + (Date.now() - startTime));
if (pendingRecipients > 0) {
await sendPendingEmail({ documentId, recipientId: recipient.id });
}
const updateDocumentStartTime = Date.now();
console.log('updateDocumentStart:' + updateDocumentStartTime);
const documents = await prisma.document.updateMany({
where: {
id: document.id,
@ -151,12 +175,26 @@ export const completeDocumentWithToken = async ({
completedAt: new Date(),
},
});
console.log('updateDocumentEnd:' + (Date.now() - updateDocumentStartTime));
console.log('Acc:' + (Date.now() - startTime));
if (documents.count > 0) {
const sealDocumentStartTime = Date.now();
console.log('sealDocumentStart:' + sealDocumentStartTime);
await sealDocument({ documentId: document.id, requestMetadata });
console.log('sealDocumentEnd:' + (Date.now() - sealDocumentStartTime));
console.log('Acc:' + (Date.now() - startTime));
}
const updateDocumentStartTime2 = Date.now();
console.log('updateDocumentStart2:' + updateDocumentStartTime2);
const updatedDocument = await getDocument({ token, documentId });
console.log('updateDocumentEnd2:' + (Date.now() - updateDocumentStartTime2));
console.log('Acc:' + (Date.now() - startTime));
const triggerWebhookStartTime = Date.now();
console.log('triggerWebhookStart:' + triggerWebhookStartTime);
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_SIGNED,
@ -164,4 +202,6 @@ export const completeDocumentWithToken = async ({
userId: updatedDocument.userId,
teamId: updatedDocument.teamId ?? undefined,
});
console.log('triggerWebhookEnd:' + (Date.now() - triggerWebhookStartTime));
console.log('Acc:' + (Date.now() - startTime));
};

View File

@ -90,41 +90,69 @@ export const sealDocument = async ({
}
// !: Need to write the fields onto the document as a hard copy
const getFileTime = Date.now();
console.log('getFileStart:' + getFileTime);
const pdfData = await getFile(documentData);
console.log('getFileEnd:' + (Date.now() - getFileTime));
const getCertificatePdfTime = Date.now();
console.log('getCertificatePdfStart:' + getCertificatePdfTime);
const certificate = await getCertificatePdf({ documentId }).then(async (doc) =>
PDFDocument.load(doc),
);
console.log('getCertificatePdfEnd:' + (Date.now() - getCertificatePdfTime));
const loadDoc = Date.now();
console.log('loadDocStart:' + loadDoc);
const doc = await PDFDocument.load(pdfData);
console.log('loadDocEnd:' + (Date.now() - loadDoc));
// Normalize and flatten layers that could cause issues with the signature
normalizeSignatureAppearances(doc);
doc.getForm().flatten();
flattenAnnotations(doc);
const certificatePageTime = Date.now();
console.log('certificatePageStart:' + certificatePageTime);
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
console.log('certificatePageEnd:' + (Date.now() - certificatePageTime));
certificatePages.forEach((page) => {
doc.addPage(page);
});
for (const field of fields) {
const insertFIeldTime = Date.now();
console.log('insertFieldStart:' + insertFIeldTime);
await insertFieldInPDF(doc, field);
console.log('insertFieldEnd:' + (Date.now() - insertFIeldTime));
}
const pdfBytes = await doc.save();
const docSaveTime = Date.now();
console.log('docSaveStart:' + docSaveTime);
const pdfBytes = await doc.save();
console.log('docSaveEnd:' + (Date.now() - docSaveTime));
const pdfBufferTIme = Date.now();
console.log('pdfBufferStart:' + pdfBufferTIme);
const pdfBuffer = await signPdf({ pdf: Buffer.from(pdfBytes) });
console.log('pdfBufferEnd:' + (Date.now() - pdfBufferTIme));
const { name, ext } = path.parse(document.title);
const putFIleTIme = Date.now();
console.log('putFileStart:' + putFIleTIme);
const { data: newData } = await putFile({
name: `${name}_signed${ext}`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(pdfBuffer),
});
console.log('putFileEnd:' + (Date.now() - putFIleTIme));
const postHog = PostHogServerClient();
if (postHog) {
@ -137,6 +165,9 @@ export const sealDocument = async ({
});
}
const updateDocumentTime = Date.now();
console.log('updateDocumentStart:' + updateDocumentTime);
await prisma.$transaction(async (tx) => {
await tx.documentData.update({
where: {
@ -160,10 +191,18 @@ export const sealDocument = async ({
});
});
console.log('updateDocumentEnd:' + (Date.now() - updateDocumentTime));
if (sendEmail && !isResealing) {
const sendCompleteEmailTime = Date.now();
console.log('sendCompleteEmailStart:' + sendCompleteEmailTime);
await sendCompletedEmail({ documentId, requestMetadata });
console.log('sendCompleteEmailEnd:' + (Date.now() - sendCompleteEmailTime));
}
const asdfasdfasdf = Date.now();
console.log('updateDocumentStart:' + asdfasdfasdf);
const updatedDocument = await prisma.document.findFirstOrThrow({
where: {
id: document.id,
@ -173,6 +212,10 @@ export const sealDocument = async ({
Recipient: true,
},
});
console.log('updateDocumentEnd:' + (Date.now() - asdfasdfasdf));
const triggerWebhookTime = Date.now();
console.log('triggerWebhookStart:' + triggerWebhookTime);
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
@ -180,4 +223,5 @@ export const sealDocument = async ({
userId: document.userId,
teamId: document.teamId ?? undefined,
});
console.log('triggerWebhookEnd:' + (Date.now() - triggerWebhookTime));
};

View File

@ -18,9 +18,13 @@ export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions
let browser: Browser;
if (process.env.NEXT_PRIVATE_BROWSERLESS_URL) {
browser = await chromium.connect(process.env.NEXT_PRIVATE_BROWSERLESS_URL);
// !: Use CDP rather than the default `connect` method to avoid coupling to the playwright version.
// !: Previously we would have to keep the playwright version in sync with the browserless version to avoid errors.
browser = await chromium.connectOverCDP(process.env.NEXT_PRIVATE_BROWSERLESS_URL);
} else {
console.log('here');
browser = await chromium.launch();
console.log('here2');
}
if (!browser) {
@ -29,17 +33,41 @@ export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions
);
}
console.log('1');
const page = await browser.newPage();
console.log('2');
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
waitUntil: 'networkidle',
});
const domcontentloadedTime = Date.now();
console.log('domcontentloadedTime:' + domcontentloadedTime);
await page
.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
waitUntil: 'domcontentloaded',
})
.catch((e) => {
console.log(e);
});
console.log('domcontentloadedEnd:' + (Date.now() - domcontentloadedTime));
const networkidleTime = Date.now();
console.log('networkidleTime:' + networkidleTime);
await page
.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
waitUntil: 'networkidle',
})
.catch((e) => {
console.log(e);
});
console.log('networkidleEnd:' + (Date.now() - networkidleTime));
const result = await page.pdf({
format: 'A4',
});
console.log(4);
void browser.close();
console.log(5);
return result;
};

View File

@ -4,6 +4,7 @@ export const getWebhooksByUserId = async (userId: number) => {
return await prisma.webhook.findMany({
where: {
userId,
teamId: null,
},
orderBy: {
createdAt: 'desc',

View File

@ -17,6 +17,7 @@ export const getFlag = async (
options?: GetFlagOptions,
): Promise<TFeatureFlagValue> => {
const requestHeaders = options?.requestHeaders ?? {};
delete requestHeaders['content-length'];
if (!isFeatureFlagEnabled()) {
return LOCAL_FEATURE_FLAGS[flag] ?? true;
@ -25,7 +26,7 @@ export const getFlag = async (
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/get`);
url.searchParams.set('flag', flag);
const response = await fetch(url, {
return await fetch(url, {
headers: {
...requestHeaders,
},
@ -35,9 +36,10 @@ export const getFlag = async (
})
.then(async (res) => res.json())
.then((res) => ZFeatureFlagValueSchema.parse(res))
.catch(() => false);
return response;
.catch((err) => {
console.error(err);
return LOCAL_FEATURE_FLAGS[flag] ?? false;
});
};
/**
@ -50,6 +52,7 @@ export const getAllFlags = async (
options?: GetFlagOptions,
): Promise<Record<string, TFeatureFlagValue>> => {
const requestHeaders = options?.requestHeaders ?? {};
delete requestHeaders['content-length'];
if (!isFeatureFlagEnabled()) {
return LOCAL_FEATURE_FLAGS;
@ -67,7 +70,10 @@ export const getAllFlags = async (
})
.then(async (res) => res.json())
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
.catch(() => LOCAL_FEATURE_FLAGS);
.catch((err) => {
console.error(err);
return LOCAL_FEATURE_FLAGS;
});
};
/**
@ -89,7 +95,10 @@ export const getAllAnonymousFlags = async (): Promise<Record<string, TFeatureFla
})
.then(async (res) => res.json())
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
.catch(() => LOCAL_FEATURE_FLAGS);
.catch((err) => {
console.error(err);
return LOCAL_FEATURE_FLAGS;
});
};
interface GetFlagOptions {