diff --git a/.jestconfig.json b/.jestconfig.json index 671bc3bdf1..a53130629a 100644 --- a/.jestconfig.json +++ b/.jestconfig.json @@ -20,8 +20,7 @@ "moduleNameMapper": { "^~/(.*)$": "/app/$1", "^@shared/(.*)$": "/shared/$1", - "^.*[.](gif|ttf|eot|svg)$": "/__test__/fileMock.js", - "^uuid$": "/node_modules/uuid/dist/index.js" + "^.*[.](gif|ttf|eot|svg)$": "/__test__/fileMock.js" }, "modulePaths": ["/app"], "setupFiles": ["/__mocks__/window.js"], @@ -48,8 +47,7 @@ "moduleNameMapper": { "^~/(.*)$": "/app/$1", "^@shared/(.*)$": "/shared/$1", - "^.*[.](gif|ttf|eot|svg)$": "/__test__/fileMock.js", - "^uuid$": "/node_modules/uuid/dist/index.js" + "^.*[.](gif|ttf|eot|svg)$": "/__test__/fileMock.js" }, "setupFiles": ["/__mocks__/window.js"], "testEnvironment": "jsdom", diff --git a/app/.oxlintrc.json b/app/.oxlintrc.json index 3939dc4aa0..439dacccee 100644 --- a/app/.oxlintrc.json +++ b/app/.oxlintrc.json @@ -5,6 +5,13 @@ { "files": ["**/*.{jsx,tsx}"], "rules": { + "no-restricted-globals": [ + "error", + { + "name": "crypto", + "message": "Do not use, does not work in environments without SSL." + } + ], "no-restricted-imports": [ "error", { diff --git a/app/actions/index.ts b/app/actions/index.ts index 7c679561bf..e51c5a9f3c 100644 --- a/app/actions/index.ts +++ b/app/actions/index.ts @@ -1,4 +1,5 @@ import { LocationDescriptor } from "history"; +import { v4 as uuidv4 } from "uuid"; import flattenDeep from "lodash/flattenDeep"; import { toast } from "sonner"; import { Optional } from "utility-types"; @@ -45,7 +46,7 @@ export function createAction(definition: Optional): Action { return definition.perform?.(context); } : undefined, - id: definition.id ?? crypto.randomUUID(), + id: definition.id ?? uuidv4(), }; } @@ -201,7 +202,7 @@ export function createActionV2( return definition.perform(context); } : () => {}, - id: definition.id ?? crypto.randomUUID(), + id: definition.id ?? uuidv4(), }; } @@ -212,7 +213,7 @@ export function createInternalLinkActionV2( ...definition, type: "action", variant: "internal_link", - id: definition.id ?? crypto.randomUUID(), + id: definition.id ?? uuidv4(), }; } @@ -223,7 +224,7 @@ export function createExternalLinkActionV2( ...definition, type: "action", variant: "external_link", - id: definition.id ?? crypto.randomUUID(), + id: definition.id ?? uuidv4(), }; } @@ -234,7 +235,7 @@ export function createActionV2WithChildren( ...definition, type: "action", variant: "action_with_children", - id: definition.id ?? crypto.randomUUID(), + id: definition.id ?? uuidv4(), }; } @@ -251,7 +252,7 @@ export function createRootMenuAction( actions: (ActionV2Variant | ActionV2Group | TActionV2Separator)[] ): ActionV2WithChildren { return { - id: crypto.randomUUID(), + id: uuidv4(), type: "action", variant: "action_with_children", name: "root_action", diff --git a/app/editor/components/MentionMenu.tsx b/app/editor/components/MentionMenu.tsx index b7505a6b08..cd603694ce 100644 --- a/app/editor/components/MentionMenu.tsx +++ b/app/editor/components/MentionMenu.tsx @@ -1,5 +1,6 @@ import { isEmail } from "class-validator"; import { observer } from "mobx-react"; +import { v4 as uuidv4 } from "uuid"; import { DocumentIcon, PlusIcon, CollectionIcon } from "outline-icons"; import { useState, useCallback, useEffect } from "react"; import { useTranslation } from "react-i18next"; @@ -99,7 +100,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) { section: UserSection, appendSpace: true, attrs: { - id: crypto.randomUUID(), + id: uuidv4(), type: MentionType.User, modelId: user.id, actorId, @@ -125,7 +126,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) { section: GroupSection, appendSpace: true, attrs: { - id: crypto.randomUUID(), + id: uuidv4(), type: MentionType.Group, modelId: group.id, actorId, @@ -157,7 +158,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) { section: DocumentsSection, appendSpace: true, attrs: { - id: crypto.randomUUID(), + id: uuidv4(), type: MentionType.Document, modelId: doc.id, actorId, @@ -185,7 +186,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) { section: CollectionsSection, appendSpace: true, attrs: { - id: crypto.randomUUID(), + id: uuidv4(), type: MentionType.Collection, modelId: collection.id, actorId, @@ -205,9 +206,9 @@ function MentionMenu({ search, isActive, ...rest }: Props) { priority: -1, appendSpace: true, attrs: { - id: crypto.randomUUID(), + id: uuidv4(), type: MentionType.Document, - modelId: crypto.randomUUID(), + modelId: uuidv4(), actorId, label: search, }, diff --git a/app/editor/components/PasteMenu.tsx b/app/editor/components/PasteMenu.tsx index 3ab7544f40..7f0bc69a65 100644 --- a/app/editor/components/PasteMenu.tsx +++ b/app/editor/components/PasteMenu.tsx @@ -1,4 +1,5 @@ import { observer } from "mobx-react"; +import { v4 as uuidv4 } from "uuid"; import { EmailIcon, LinkIcon } from "outline-icons"; import React, { useCallback } from "react"; import { useTranslation } from "react-i18next"; @@ -101,11 +102,11 @@ function useItems({ icon: , visible: !!mentionType, attrs: { - id: crypto.randomUUID(), + id: uuidv4(), type: mentionType, label: pastedText, href: pastedText, - modelId: crypto.randomUUID(), + modelId: uuidv4(), actorId: user?.id, }, appendSpace: true, diff --git a/app/editor/extensions/PasteHandler.tsx b/app/editor/extensions/PasteHandler.tsx index f049a8665d..41e814b680 100644 --- a/app/editor/extensions/PasteHandler.tsx +++ b/app/editor/extensions/PasteHandler.tsx @@ -1,4 +1,5 @@ import { action, observable } from "mobx"; +import { v4 as uuidv4 } from "uuid"; import { toggleMark } from "prosemirror-commands"; import { Node, Slice } from "prosemirror-model"; import { @@ -143,7 +144,7 @@ export default class PasteHandler extends Extension { type: MentionType.Document, modelId: document.id, label: document.titleWithDefault, - id: crypto.randomUUID(), + id: uuidv4(), }) ) ); @@ -188,7 +189,7 @@ export default class PasteHandler extends Extension { type: MentionType.Collection, modelId: collection.id, label: collection.name, - id: crypto.randomUUID(), + id: uuidv4(), }) ) ); diff --git a/app/scenes/Document/components/CommentForm.tsx b/app/scenes/Document/components/CommentForm.tsx index a239b13ca5..6b4bde99b3 100644 --- a/app/scenes/Document/components/CommentForm.tsx +++ b/app/scenes/Document/components/CommentForm.tsx @@ -1,4 +1,5 @@ import * as VisuallyHidden from "@radix-ui/react-visually-hidden"; +import { v4 as uuidv4 } from "uuid"; import { m } from "framer-motion"; import { action } from "mobx"; import { observer } from "mobx-react"; @@ -160,7 +161,7 @@ function CommentForm({ comments ); - comment.id = crypto.randomUUID(); + comment.id = uuidv4(); comments.add(comment); comment diff --git a/app/scenes/Search/Search.tsx b/app/scenes/Search/Search.tsx index 6f15941de6..5cdd2a36ec 100644 --- a/app/scenes/Search/Search.tsx +++ b/app/scenes/Search/Search.tsx @@ -1,4 +1,5 @@ import { observer } from "mobx-react"; +import { v4 as uuidv4 } from "uuid"; import queryString from "query-string"; import * as React from "react"; import { useTranslation } from "react-i18next"; @@ -104,7 +105,7 @@ function Search() { // without a flash of loading. if (query) { searches.add({ - id: crypto.randomUUID(), + id: uuidv4(), query, createdAt: new Date().toISOString(), }); diff --git a/app/stores/DialogsStore.ts b/app/stores/DialogsStore.ts index 13a45eca6b..58a802eaf9 100644 --- a/app/stores/DialogsStore.ts +++ b/app/stores/DialogsStore.ts @@ -1,4 +1,5 @@ import { observable, action } from "mobx"; +import { v4 as uuidv4 } from "uuid"; import * as React from "react"; type DialogDefinition = { @@ -65,7 +66,7 @@ export default class DialogsStore { this.modalStack.clear(); } - this.modalStack.set(id ?? replaceId ?? crypto.randomUUID(), { + this.modalStack.set(id ?? replaceId ?? uuidv4(), { title, content, style, diff --git a/package.json b/package.json index f21188d3ad..f5ace33862 100644 --- a/package.json +++ b/package.json @@ -262,6 +262,7 @@ "ukkonen": "^2.2.0", "umzug": "^3.8.2", "utility-types": "^3.11.0", + "uuid": "^11.1.0", "validator": "13.15.20", "vaul": "^1.1.2", "vite": "npm:rolldown-vite@latest", diff --git a/shared/.oxlintrc.json b/shared/.oxlintrc.json index 6cfeec44bf..7351738e9d 100644 --- a/shared/.oxlintrc.json +++ b/shared/.oxlintrc.json @@ -5,6 +5,13 @@ { "files": ["**/*.{js,jsx,ts,tsx}"], "rules": { + "no-restricted-globals": [ + "error", + { + "name": "crypto", + "message": "Do not use, does not work in environments without SSL." + } + ], "no-restricted-imports": [ "error", { diff --git a/shared/editor/commands/comment.ts b/shared/editor/commands/comment.ts index 38babf76a4..a37cc4baad 100644 --- a/shared/editor/commands/comment.ts +++ b/shared/editor/commands/comment.ts @@ -1,5 +1,6 @@ import { Attrs } from "prosemirror-model"; import { Command, NodeSelection, TextSelection } from "prosemirror-state"; +import { v4 as uuidv4 } from "uuid"; import { isMarkActive } from "../queries/isMarkActive"; import { chainTransactions } from "../lib/chainTransactions"; import { addMark } from "./addMark"; @@ -20,7 +21,7 @@ const addCommentNodeSelection = const newMark = { type: "comment", attrs: { - id: crypto.randomUUID(), + id: uuidv4(), userId: attrs.userId, draft: true, }, @@ -54,7 +55,7 @@ const addCommentTextSelection = chainTransactions( addMark(state.schema.marks.comment, { - id: crypto.randomUUID(), + id: uuidv4(), userId: attrs.userId, draft: true, }), diff --git a/shared/editor/commands/insertFiles.ts b/shared/editor/commands/insertFiles.ts index 1b981f0914..77f740deb0 100644 --- a/shared/editor/commands/insertFiles.ts +++ b/shared/editor/commands/insertFiles.ts @@ -1,4 +1,5 @@ import * as Sentry from "@sentry/react"; +import { v4 as uuidv4 } from "uuid"; import { EditorView } from "prosemirror-view"; import { toast } from "sonner"; import type { Dictionary } from "~/hooks/useDictionary"; @@ -71,7 +72,7 @@ const insertFiles = async function ( : undefined; return { - id: `upload-${crypto.randomUUID()}`, + id: `upload-${uuidv4()}`, dimensions: await getDimensions?.(file), isImage, isVideo, diff --git a/shared/editor/extensions/Mermaid.ts b/shared/editor/extensions/Mermaid.ts index c774b5b102..b32b181e6c 100644 --- a/shared/editor/extensions/Mermaid.ts +++ b/shared/editor/extensions/Mermaid.ts @@ -1,6 +1,7 @@ import debounce from "lodash/debounce"; import last from "lodash/last"; import sortBy from "lodash/sortBy"; +import { v4 as uuidv4 } from "uuid"; import type MermaidUnsafe from "mermaid"; import { Node } from "prosemirror-model"; import { @@ -53,7 +54,7 @@ class MermaidRenderer { readonly editor: Editor; constructor(editor: Editor) { - this.diagramId = crypto.randomUUID(); + this.diagramId = uuidv4(); this.elementId = `mermaid-diagram-wrapper-${this.diagramId}`; this.element = document.getElementById(this.elementId) || document.createElement("div"); diff --git a/shared/editor/lib/mention.ts b/shared/editor/lib/mention.ts index a5530b4580..a8aeac9630 100644 --- a/shared/editor/lib/mention.ts +++ b/shared/editor/lib/mention.ts @@ -1,5 +1,6 @@ import { Node, Schema } from "prosemirror-model"; import { Primitive } from "utility-types"; +import { v4 as uuidv4 } from "uuid"; import { isList } from "../queries/isList"; export function transformListToMentions( @@ -33,11 +34,11 @@ function transformListItemToMentions( node.type.create( node.attrs, schema.nodes.mention.create({ - id: crypto.randomUUID(), + id: uuidv4(), type: mentionType, label: link, href: link, - modelId: crypto.randomUUID(), + modelId: uuidv4(), actorId: attrs.actorId, }) ) diff --git a/shared/editor/marks/Comment.ts b/shared/editor/marks/Comment.ts index 4c3a6f77ab..d789894577 100644 --- a/shared/editor/marks/Comment.ts +++ b/shared/editor/marks/Comment.ts @@ -1,12 +1,13 @@ import { toggleMark } from "prosemirror-commands"; import { MarkSpec, MarkType, Mark as PMMark } from "prosemirror-model"; import { Command, Plugin } from "prosemirror-state"; +import { v4 as uuidv4 } from "uuid"; import { collapseSelection } from "../commands/collapseSelection"; +import { addComment } from "../commands/comment"; import { chainTransactions } from "../lib/chainTransactions"; import { isMarkActive } from "../queries/isMarkActive"; import { EditorStyleHelper } from "../styles/EditorStyleHelper"; import Mark from "./Mark"; -import { addComment } from "../commands/comment"; export default class Comment extends Mark { get name() { @@ -81,7 +82,7 @@ export default class Comment extends Mark { chainTransactions( toggleMark(type, { - id: crypto.randomUUID(), + id: uuidv4(), userId: this.options.userId, draft: true, }), diff --git a/shared/editor/nodes/CheckboxItem.ts b/shared/editor/nodes/CheckboxItem.ts index 92d7bae257..59d2ac178c 100644 --- a/shared/editor/nodes/CheckboxItem.ts +++ b/shared/editor/nodes/CheckboxItem.ts @@ -5,6 +5,7 @@ import { sinkListItem, liftListItem, } from "prosemirror-schema-list"; +import { v4 as uuidv4 } from "uuid"; import toggleCheckboxItem from "../commands/toggleCheckboxItem"; import { MarkdownSerializerState } from "../lib/markdown/serializer"; import checkboxRule from "../rules/checkboxes"; @@ -34,7 +35,7 @@ export default class CheckboxItem extends Node { }, ], toDOM: (node) => { - const id = `checkbox-${crypto.randomUUID()}`; + const id = `checkbox-${uuidv4()}`; const checked = node.attrs.checked.toString(); let input; if (typeof document !== "undefined") { diff --git a/shared/editor/nodes/Mention.tsx b/shared/editor/nodes/Mention.tsx index c23fe499f6..d10d8b93a8 100644 --- a/shared/editor/nodes/Mention.tsx +++ b/shared/editor/nodes/Mention.tsx @@ -13,6 +13,7 @@ import { TextSelection, } from "prosemirror-state"; import { Primitive } from "utility-types"; +import { v4 as uuidv4 } from "uuid"; import env from "../../env"; import { MentionType, UnfurlResourceType, UnfurlResponse } from "../../types"; import { @@ -178,7 +179,7 @@ export default class Mention extends Node { node.type.name === this.name && (!nodeId || existingIds.has(nodeId)) ) { - nodeId = crypto.randomUUID(); + nodeId = uuidv4(); modified = true; tr.setNodeAttribute(pos, "id", nodeId); } diff --git a/shared/random.ts b/shared/random.ts index 8ec12406ea..fc7aa375ea 100644 --- a/shared/random.ts +++ b/shared/random.ts @@ -61,6 +61,7 @@ export const randomString = (options: number | RandomStringOptions) => { : lowercase + uppercase + numeric; const array = new Uint8Array(length); + // oxlint-disable-next-line no-restricted-globals crypto.getRandomValues(array); return Array.from(array, (x) => chars[x % chars.length]).join(""); }; diff --git a/yarn.lock b/yarn.lock index 84275c71da..00bf868de5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -753,20 +753,7 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz#3e747434ea007910c320c4d39a6b46f20f371d46" - integrity sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-member-expression-to-functions" "^7.27.1" - "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/traverse" "^7.28.3" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.28.5": +"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3", "@babel/helper-create-class-features-plugin@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46" integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ== @@ -818,15 +805,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@babel/helper-member-expression-to-functions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" - integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-member-expression-to-functions@^7.28.5": +"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== @@ -894,12 +873,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== - -"@babel/helper-validator-identifier@^7.28.5": +"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==