diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json
index f68318fbe..d1f3a642b 100644
--- a/packages/core/package-lock.json
+++ b/packages/core/package-lock.json
@@ -33,6 +33,7 @@
"@types/event-source-polyfill": "^1.0.1",
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.2",
+ "@types/mime-db": "^1.43.1",
"@types/prismjs": "^1.26.0",
"@types/spark-md5": "^3.0.2",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
@@ -1688,6 +1689,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/mime-db": {
+ "version": "1.43.5",
+ "resolved": "https://registry.npmjs.org/@types/mime-db/-/mime-db-1.43.5.tgz",
+ "integrity": "sha512-/bfTiIUTNPUBnwnYvUxXAre5MhD88jgagLEQiQtIASjU+bwxd8kS/ASDA4a8ufd8m0Lheu6eeMJHEUpLHoJ28A==",
+ "dev": true
+ },
"node_modules/@types/node": {
"version": "18.11.9",
"dev": true,
diff --git a/packages/core/package.json b/packages/core/package.json
index 81fbb5896..03fd7bcb6 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -12,6 +12,7 @@
"@types/event-source-polyfill": "^1.0.1",
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.2",
+ "@types/mime-db": "^1.43.1",
"@types/prismjs": "^1.26.0",
"@types/spark-md5": "^3.0.2",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
diff --git a/packages/core/src/database/cached-collection.ts b/packages/core/src/database/cached-collection.ts
index 410a6202e..7e6ba538f 100644
--- a/packages/core/src/database/cached-collection.ts
+++ b/packages/core/src/database/cached-collection.ts
@@ -18,7 +18,6 @@ along with this program. If not, see .
*/
import { IndexedCollection } from "./indexed-collection";
-import MapStub from "../utils/map";
import {
CollectionType,
Collections,
@@ -51,9 +50,7 @@ export class CachedCollection<
const data = await this.collection.indexer.readMulti(
this.collection.indexer.indices
);
- if ("dispose" in this.cache && typeof this.cache.dispose === "function")
- this.cache.dispose();
- this.cache = new MapStub.Map(data);
+ this.cache = new Map(data);
}
async add(item: MaybeDeletedItem) {
diff --git a/packages/core/src/logger.js b/packages/core/src/logger.ts
similarity index 72%
rename from packages/core/src/logger.js
rename to packages/core/src/logger.ts
index e0fc6b047..31588b4fc 100644
--- a/packages/core/src/logger.js
+++ b/packages/core/src/logger.ts
@@ -18,13 +18,17 @@ along with this program. If not, see .
*/
import {
+ ILogReporter,
LogLevel,
+ LogMessage,
Logger,
NoopLogger,
combineReporters,
consoleReporter,
- format
+ format,
+ ILogger
} from "@notesnook/logger";
+import { IStorage } from "./interfaces";
const WEEK = 86400000 * 7;
@@ -35,32 +39,21 @@ const WEEK = 86400000 * 7;
// 4. Implement functions for log retrieval & filtering
class DatabaseLogReporter {
- /**
- *
- * @param {import("./database/crypto").default} storage
- */
- constructor(storage) {
+ writer: DatabaseLogWriter;
+ constructor(storage: IStorage) {
this.writer = new DatabaseLogWriter(storage);
}
- /**
- *
- * @param {import("@notesnook/logger").LogMessage} log
- */
- write(log) {
+ write(log: LogMessage) {
this.writer.push(log);
}
}
class DatabaseLogWriter {
- /**
- *
- * @param {import("./database/crypto").default} storage
- */
- constructor(storage) {
- this.storage = storage;
- this.queue = new Map();
- this.hasCleared = false;
+ private queue: Map = new Map();
+ private hasCleared = false;
+
+ constructor(private readonly storage: IStorage) {
setInterval(() => {
setTimeout(() => {
if (!this.hasCleared) {
@@ -72,7 +65,7 @@ class DatabaseLogWriter {
}, 10000);
}
- push(message) {
+ push(message: LogMessage) {
const key = new Date(message.timestamp).toLocaleDateString();
this.queue.set(`${key}:${message.timestamp}`, message);
}
@@ -100,33 +93,27 @@ class DatabaseLogWriter {
}
class DatabaseLogManager {
- /**
- *
- * @param {import("./database/crypto").default} storage
- */
- constructor(storage) {
- this.storage = storage;
- }
+ constructor(private readonly storage: IStorage) {}
async get() {
const logKeys = await this.storage.getAllKeys();
- const logs = await this.storage.readMulti(logKeys);
- const logGroups = {};
+ const logEntries = await this.storage.readMulti(logKeys);
+ const logs: Record = {};
- for (const [key, log] of logs) {
- const keyParts = key.split(":");
+ for (const [logKey, log] of logEntries) {
+ const keyParts = logKey.split(":");
if (keyParts.length === 1) continue;
- const groupKey = keyParts[0];
- if (!logGroups[groupKey]) logGroups[groupKey] = [];
- logGroups[groupKey].push(log);
+ const key = keyParts[0];
+ if (!logs[key]) logs[key] = [];
+ logs[key].push(log);
}
- return Object.keys(logGroups)
+ return Object.keys(logs)
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
.map((key) => ({
key,
- logs: logGroups[key]?.sort((a, b) => a.timestamp - b.timestamp)
+ logs: logs[key]?.sort((a, b) => a.timestamp - b.timestamp)
}));
}
@@ -135,7 +122,7 @@ class DatabaseLogManager {
await this.storage.removeMulti(logKeys);
}
- async delete(key) {
+ async delete(key: string) {
const logKeys = await this.storage.getAllKeys();
const keysToRemove = [];
for (const logKey of logKeys) {
@@ -148,9 +135,9 @@ class DatabaseLogManager {
}
}
-function initialize(storage, disableConsoleLogs) {
+function initialize(storage: IStorage, disableConsoleLogs?: boolean) {
if (storage) {
- let reporters = [new DatabaseLogReporter(storage)];
+ const reporters: ILogReporter[] = [new DatabaseLogReporter(storage)];
if (process.env.NODE_ENV !== "production" && !disableConsoleLogs)
reporters.push(consoleReporter);
logger = new Logger({
@@ -161,14 +148,7 @@ function initialize(storage, disableConsoleLogs) {
}
}
-/**
- * @type {import("@notesnook/logger").ILogger}
- */
-var logger = new NoopLogger();
-
-/**
- * @type {DatabaseLogManager | undefined}
- */
-var logManager;
+let logger: ILogger = new NoopLogger();
+let logManager: DatabaseLogManager | undefined = undefined;
export { LogLevel, format, initialize, logManager, logger };
diff --git a/packages/core/src/utils/map.js b/packages/core/src/utils/buffer-pool.ts
similarity index 70%
rename from packages/core/src/utils/map.js
rename to packages/core/src/utils/buffer-pool.ts
index f93568f86..dfae03a3e 100644
--- a/packages/core/src/utils/map.js
+++ b/packages/core/src/utils/buffer-pool.ts
@@ -17,15 +17,20 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-var map = Map;
-class MapStub {
- override(replacement) {
- map = replacement;
+export class BufferPool {
+ private freeBuffers: Buffer[] = [];
+ constructor(private readonly size: number) {}
+
+ alloc() {
+ return this.freeBuffers.pop() || this.allocNew();
}
- get Map() {
- return map;
+ private allocNew() {
+ return Buffer.alloc(this.size);
+ }
+
+ free(buf: Buffer) {
+ this.freeBuffers.push(buf);
+ return true;
}
}
-
-export default new MapStub();
diff --git a/packages/core/src/utils/constants.js b/packages/core/src/utils/constants.ts
similarity index 77%
rename from packages/core/src/utils/constants.js
rename to packages/core/src/utils/constants.ts
index adbfe225e..1533c1aea 100644
--- a/packages/core/src/utils/constants.js
+++ b/packages/core/src/utils/constants.ts
@@ -45,14 +45,15 @@ export const hosts = {
export default hosts;
-export const getServerNameFromHost = (host) => {
- const names = {
- [extractHostname(hosts.API_HOST)]: "Notesnook Sync Server",
- [extractHostname(hosts.AUTH_HOST)]: "Authentication Server",
- [extractHostname(hosts.SSE_HOST)]: "Eventing Server",
- [extractHostname(hosts.SUBSCRIPTIONS_HOST)]:
- "Subscriptions Management Server",
- [extractHostname(hosts.ISSUES_HOST)]: "Bug Reporting Server"
- };
- return names[host];
+const HOSTNAMES = {
+ [extractHostname(hosts.API_HOST)]: "Notesnook Sync Server",
+ [extractHostname(hosts.AUTH_HOST)]: "Authentication Server",
+ [extractHostname(hosts.SSE_HOST)]: "Eventing Server",
+ [extractHostname(hosts.SUBSCRIPTIONS_HOST)]:
+ "Subscriptions Management Server",
+ [extractHostname(hosts.ISSUES_HOST)]: "Bug Reporting Server"
+};
+
+export const getServerNameFromHost = (host: string) => {
+ return HOSTNAMES[host];
};
diff --git a/packages/core/src/utils/filename.js b/packages/core/src/utils/filename.ts
similarity index 81%
rename from packages/core/src/utils/filename.js
rename to packages/core/src/utils/filename.ts
index a16a7cf82..2b416b308 100644
--- a/packages/core/src/utils/filename.js
+++ b/packages/core/src/utils/filename.ts
@@ -16,22 +16,13 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+
import db from "mime-db";
-// type MimeTypeInfo = {
-// source: string;
-// extensions?: string[];
-// charset?: string;
-// compressible?: boolean;
-// };
-
-/**
- *
- * @param {string} filename
- * @param {string | undefined} mime
- * @returns {string}
- */
-export function getFileNameWithExtension(filename, mime) {
+export function getFileNameWithExtension(
+ filename: string,
+ mime: string | undefined
+): string {
if (!mime || mime === "application/octet-stream") return filename;
const mimeData = db[mime];
if (!mimeData || !mimeData.extensions || mimeData.extensions.length === 0)
@@ -59,24 +50,23 @@ export const DocumentMimeTypes = [
"application/vnd.oasis.opendocument.presentation"
];
-export const WebClipMimeType = "application/vnd.notesnook.web-clip";
-
-export function isDocument(mime) {
+export function isDocument(mime: string) {
return DocumentMimeTypes.some((a) => a.startsWith(mime));
}
-export function isWebClip(mime) {
+export const WebClipMimeType = "application/vnd.notesnook.web-clip";
+export function isWebClip(mime: string) {
return mime === WebClipMimeType;
}
-export function isImage(mime) {
+export function isImage(mime: string) {
return mime.startsWith("image/");
}
-export function isVideo(mime) {
+export function isVideo(mime: string) {
return mime.startsWith("video/");
}
-export function isAudio(mime) {
+export function isAudio(mime: string) {
return mime.startsWith("audio/");
}
diff --git a/packages/core/src/utils/hostname.js b/packages/core/src/utils/hostname.ts
similarity index 79%
rename from packages/core/src/utils/hostname.js
rename to packages/core/src/utils/hostname.ts
index bbc1dd2b1..6b6711732 100644
--- a/packages/core/src/utils/hostname.js
+++ b/packages/core/src/utils/hostname.ts
@@ -17,20 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-export function extractHostname(url) {
- var hostname;
+export function extractHostname(url: string) {
//find & remove protocol (http, ftp, etc.) and get hostname
-
- if (url.indexOf("//") > -1) {
- hostname = url.split("/")[2];
- } else {
- hostname = url.split("/")[0];
- }
+ const hostname: string =
+ url.indexOf("//") > -1 ? url.split("/")[2] : url.split("/")[0];
//find & remove port number
// hostname = hostname.split(":")[0];
//find & remove "?"
- hostname = hostname.split("?")[0];
-
- return hostname;
+ return hostname.split("?")[0];
}
diff --git a/packages/core/src/utils/http.js b/packages/core/src/utils/http.ts
similarity index 59%
rename from packages/core/src/utils/http.js
rename to packages/core/src/utils/http.ts
index 28497facf..ce256919c 100644
--- a/packages/core/src/utils/http.js
+++ b/packages/core/src/utils/http.ts
@@ -22,28 +22,43 @@ import { logger } from "../logger";
import { getServerNameFromHost } from "./constants";
import { extractHostname } from "./hostname";
-function get(url, token) {
- return request(url, token, "GET");
+type ContentType = "application/json" | "application/x-www-form-urlencoded";
+type RequestBody = Record | null;
+type JsonRequestBody = Record | null;
+function get(url: string, token?: string) {
+ return request(url, "GET", token);
}
-function deleteRequest(url, token) {
- return request(url, token, "DELETE");
+function deleteRequest(url: string, token?: string) {
+ return request(url, "DELETE", token);
}
-function patch(url, data, token) {
- return bodyRequest(url, data, token, "PATCH");
+function patch(url: string, data: RequestBody, token?: string) {
+ return bodyRequest(url, transformFormData(data), token, "PATCH");
}
-patch.json = function (url, data, token) {
- return bodyRequest(url, data, token, "PATCH", "application/json");
+patch.json = function (url: string, data: JsonRequestBody, token?: string) {
+ return bodyRequest(
+ url,
+ transformJson(data),
+ token,
+ "PATCH",
+ "application/json"
+ );
};
-function post(url, data, token) {
- return bodyRequest(url, data, token, "POST");
+function post(url: string, data: RequestBody, token?: string) {
+ return bodyRequest(url, transformFormData(data), token, "POST");
}
-post.json = function (url, data, token) {
- return bodyRequest(url, data, token, "POST", "application/json");
+post.json = function (url: string, data: JsonRequestBody, token?: string) {
+ return bodyRequest(
+ url,
+ transformJson(data),
+ token,
+ "POST",
+ "application/json"
+ );
};
export default {
@@ -53,84 +68,40 @@ export default {
patch
};
-function transformer(data, type) {
- if (!data) return;
- if (type === "application/json") return JSON.stringify(data);
- else {
- return Object.entries(data)
- .map(([key, value]) =>
- value ? `${encodeURIComponent(key)}=${encodeURIComponent(value)}` : ""
- )
- .join("&");
- }
-}
-
-/**
- *
- * @param {Response} response
- * @returns
- */
-async function handleResponse(response) {
- try {
- const contentType = response.headers.get("content-type");
- if (contentType && contentType.includes("application/json")) {
- const json = await response.json();
- if (response.ok) {
- return json;
- }
- throw new RequestError(errorTransformer(json));
- } else {
- if (response.status === 429)
- throw new Error("You are being rate limited.");
-
- if (response.ok) return await response.text();
- else if (response.status === 401) {
- EV.publish(EVENTS.userUnauthorized, response.url);
- throw new Error("Unauthorized.");
- } else
- throw new Error(
- `Request failed with status code: ${response.status} ${response.statusText}.`
- );
- }
- } catch (e) {
- logger.error(e, "Error while sending request:", { url: response.url });
- throw e;
- }
-}
-
-async function request(url, token, method) {
+async function request(url: string, method: "GET" | "DELETE", token?: string) {
return handleResponse(
await fetchWrapped(url, {
method,
- headers: getAuthorizationHeader(token)
+ headers: getHeaders(token)
})
);
}
async function bodyRequest(
- url,
- data,
- token,
- method,
- contentType = "application/x-www-form-urlencoded"
+ url: string,
+ data: string | undefined,
+ token: string | undefined,
+ method: "POST" | "PATCH" | "PUT",
+ contentType: ContentType = "application/x-www-form-urlencoded"
) {
return handleResponse(
await fetchWrapped(url, {
method,
- body: transformer(data, contentType),
+ body: data,
headers: {
- ...getAuthorizationHeader(token),
+ ...getHeaders(token),
"Content-Type": contentType
}
})
);
}
-function getAuthorizationHeader(token) {
- return token ? { Authorization: "Bearer " + token } : {};
-}
-
-export function errorTransformer(errorJson) {
+export function errorTransformer(errorJson: {
+ error?: string;
+ errors?: string[];
+ error_description?: string;
+ data?: string;
+}) {
let errorMessage = "Unknown error.";
let errorCode = "unknown";
@@ -156,8 +127,8 @@ export function errorTransformer(errorJson) {
}
}
default:
- errorMessage = error_description || error || "An unknown error occurred.";
- errorCode = error;
+ errorMessage = error_description || error || errorMessage;
+ errorCode = error || errorCode;
break;
}
@@ -168,12 +139,7 @@ export function errorTransformer(errorJson) {
};
}
-/**
- *
- * @param {RequestInfo} input
- * @param {RequestInit} init
- */
-async function fetchWrapped(input, init) {
+async function fetchWrapped(input: string, init: RequestInit) {
try {
const response = await fetch(input, init);
return response;
@@ -182,17 +148,73 @@ async function fetchWrapped(input, init) {
const serverName = getServerNameFromHost(host);
if (serverName)
throw new Error(
- `${serverName} is not responding. Please check your internet connection. If the problem persists, feel free email us at support@streetwriters.co. (Reference error: ${e.message})`
+ `${serverName} is not responding. Please check your internet connection. If the problem persists, feel free email us at support@streetwriters.co. (Reference error: ${
+ (e as Error).message
+ })`
);
throw e;
}
}
+async function handleResponse(response: Response) {
+ try {
+ const contentType = response.headers.get("content-type");
+ if (contentType && contentType.includes("application/json")) {
+ const json = await response.json();
+ if (response.ok) {
+ return json;
+ }
+ throw new RequestError(errorTransformer(json));
+ } else {
+ if (response.status === 429)
+ throw new Error("You are being rate limited.");
+
+ if (response.ok) return await response.text();
+ else if (response.status === 401) {
+ EV.publish(EVENTS.userUnauthorized, response.url);
+ throw new Error("Unauthorized.");
+ } else
+ throw new Error(
+ `Request failed with status code: ${response.status} ${response.statusText}.`
+ );
+ }
+ } catch (e) {
+ logger.error(e as Error, "Error while sending request:", {
+ url: response.url
+ });
+ throw e;
+ }
+}
+
export class RequestError extends Error {
- constructor(error) {
+ code: string;
+ data: unknown;
+ constructor(error: { code: string; data: unknown; description: string }) {
super(error.description);
this.code = error.code;
this.data = error.data;
}
}
+
+function getHeaders(token?: string | null) {
+ return token ? { Authorization: "Bearer " + token } : undefined;
+}
+
+function transformJson(data: JsonRequestBody) {
+ return JSON.stringify(data);
+}
+
+function transformFormData(data: RequestBody) {
+ if (data) {
+ return Object.entries(data)
+ .map(([key, value]) =>
+ value
+ ? `${encodeURIComponent(key)}=${
+ value ? encodeURIComponent(value) : ""
+ }`
+ : ""
+ )
+ .join("&");
+ }
+}
diff --git a/packages/core/src/utils/id.ts b/packages/core/src/utils/id.ts
index daece54b6..761210a14 100644
--- a/packages/core/src/utils/id.ts
+++ b/packages/core/src/utils/id.ts
@@ -18,12 +18,10 @@ along with this program. If not, see .
*/
import SparkMD5 from "spark-md5";
-import ObjectID from "./object-id";
+import { createObjectId } from "./object-id";
export function getId(time?: number) {
- if (time)
- return new ObjectID(new ObjectID().generate(time / 1000)).toHexString();
- return new ObjectID().toHexString();
+ return createObjectId(time);
}
export function makeId(text: string) {
diff --git a/packages/core/src/utils/object-id.js b/packages/core/src/utils/object-id.js
deleted file mode 100644
index 3a3a6c0bc..000000000
--- a/packages/core/src/utils/object-id.js
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
-This file is part of the Notesnook project (https://notesnook.com/)
-
-Copyright (C) 2023 Streetwriters (Private) Limited
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-// Copied from https://github.com/williamkapke/bson-objectid
-
-import { randomInt } from "./random";
-
-var MACHINE_ID = randomInt();
-var index = randomInt();
-var pid =
- (typeof process === "undefined" || typeof process.pid !== "number"
- ? randomInt()
- : process.pid) % 0xffff;
-
-/**
- * Determine if an object is Buffer
- *
- * Author: Feross Aboukhadijeh
- * License: MIT
- *
- */
-var isBuffer = function (obj) {
- return !!(
- obj != null &&
- obj.constructor &&
- typeof obj.constructor.isBuffer === "function" &&
- obj.constructor.isBuffer(obj)
- );
-};
-
-// Precomputed hex table enables speedy hex string conversion
-var hexTable = [];
-for (var i = 0; i < 256; i++) {
- hexTable[i] = (i <= 15 ? "0" : "") + i.toString(16);
-}
-
-// Regular expression that checks for hex value
-var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
-
-// Lookup tables
-var decodeLookup = [];
-i = 0;
-while (i < 10) decodeLookup[0x30 + i] = i++;
-while (i < 16) decodeLookup[0x41 - 10 + i] = decodeLookup[0x61 - 10 + i] = i++;
-
-/**
- * Create a new immutable ObjectID instance
- *
- * @class Represents the BSON ObjectID type
- * @param {String|Number} id Can be a 24 byte hex string, 12 byte binary string or a Number.
- * @return {Object} instance of ObjectID.
- */
-class ObjectID {
- constructor(id) {
- if (!(this instanceof ObjectID)) return new ObjectID(id);
- if (id && (id instanceof ObjectID || id._bsontype === "ObjectID"))
- return id;
-
- this._bsontype = "ObjectID";
-
- // The most common usecase (blank id, new objectId instance)
- if (id == null || typeof id === "number") {
- // Generate a new id
- this.id = this.generate(id);
- // Return the object
- return;
- }
-
- // Check if the passed in id is valid
- var valid = ObjectID.isValid(id);
-
- // Throw an error if it's not a valid setup
- if (!valid && id != null) {
- throw new Error(
- "Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"
- );
- } else if (valid && typeof id === "string" && id.length === 24) {
- return ObjectID.createFromHexString(id);
- } else if (id != null && id.length === 12) {
- // assume 12 byte string
- this.id = id;
- } else if (id != null && typeof id.toHexString === "function") {
- // Duck-typing to support ObjectId from different npm packages
- return id;
- } else {
- throw new Error(
- "Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"
- );
- }
- }
- /**
- * Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID.
- *
- * @param {Number} time an integer number representing a number of seconds.
- * @return {ObjectID} return the created ObjectID
- * @api public
- */
- static createFromTime(time) {
- time = parseInt(time, 10) % 0xffffffff;
- return new ObjectID(hex(8, time) + "0000000000000000");
- }
- /**
- * Creates an ObjectID from a hex string representation of an ObjectID.
- *
- * @param {String} hexString create a ObjectID from a passed in 24 byte hexstring.
- * @return {ObjectID} return the created ObjectID
- * @api public
- */
- static createFromHexString(hexString) {
- // Throw an error if it's not a valid setup
- if (
- typeof hexString === "undefined" ||
- (hexString != null && hexString.length !== 24)
- ) {
- throw new Error(
- "Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"
- );
- }
-
- // Calculate lengths
- var data = "";
- var i = 0;
-
- while (i < 24) {
- data += String.fromCharCode(
- (decodeLookup[hexString.charCodeAt(i++)] << 4) |
- decodeLookup[hexString.charCodeAt(i++)]
- );
- }
-
- return new ObjectID(data);
- }
- /**
- * Checks if a value is a valid bson ObjectId
- *
- * @param {String} objectid Can be a 24 byte hex string or an instance of ObjectID.
- * @return {Boolean} return true if the value is a valid bson ObjectID, return false otherwise.
- * @api public
- *
- * THE NATIVE DOCUMENTATION ISN'T CLEAR ON THIS GUY!
- * http://mongodb.github.io/node-mongodb-native/api-bson-generated/objectid.html#objectid-isvalid
- */
- static isValid(id) {
- if (id == null) return false;
-
- if (typeof id === "number") {
- return true;
- }
-
- if (typeof id === "string") {
- return (
- id.length === 12 || (id.length === 24 && checkForHexRegExp.test(id))
- );
- }
-
- if (id instanceof ObjectID) {
- return true;
- }
-
- if (isBuffer(id)) {
- return true;
- }
-
- // Duck-Typing detection of ObjectId like objects
- if (
- typeof id.toHexString === "function" &&
- (id.id instanceof Buffer || typeof id.id === "string")
- ) {
- return (
- id.id.length === 12 ||
- (id.id.length === 24 && checkForHexRegExp.test(id.id))
- );
- }
-
- return false;
- }
- /**
- * Return the ObjectID id as a 24 byte hex string representation
- *
- * @return {String} return the 24 byte hex string representation.
- * @api public
- */
- toHexString() {
- if (!this.id || !this.id.length) {
- throw new Error(
- "invalid ObjectId, ObjectId.id must be either a string or a Buffer, but is [" +
- JSON.stringify(this.id) +
- "]"
- );
- }
-
- if (this.id.length === 24) {
- return this.id;
- }
-
- if (isBuffer(this.id)) {
- return this.id.toString("hex");
- }
-
- var hexString = "";
- for (var i = 0; i < this.id.length; i++) {
- hexString += hexTable[this.id.charCodeAt(i)];
- }
-
- return hexString;
- }
- /**
- * Compares the equality of this ObjectID with `otherID`.
- *
- * @param {Object} otherId ObjectID instance to compare against.
- * @return {Boolean} the result of comparing two ObjectID's
- * @api public
- */
- equals(otherId) {
- if (otherId instanceof ObjectID) {
- return this.toString() === otherId.toString();
- } else if (
- typeof otherId === "string" &&
- ObjectID.isValid(otherId) &&
- otherId.length === 12 &&
- isBuffer(this.id)
- ) {
- return otherId === this.id.toString("binary");
- } else if (
- typeof otherId === "string" &&
- ObjectID.isValid(otherId) &&
- otherId.length === 24
- ) {
- return otherId.toLowerCase() === this.toHexString();
- } else if (
- typeof otherId === "string" &&
- ObjectID.isValid(otherId) &&
- otherId.length === 12
- ) {
- return otherId === this.id;
- } else if (
- otherId != null &&
- (otherId instanceof ObjectID || otherId.toHexString)
- ) {
- return otherId.toHexString() === this.toHexString();
- } else {
- return false;
- }
- }
- /**
- * Returns the generation date (accurate up to the second) that this ID was generated.
- *
- * @return {Date} the generation date
- * @api public
- */
- getTimestamp() {
- var timestamp = new Date();
- var time;
- if (isBuffer(this.id)) {
- time =
- this.id[3] |
- (this.id[2] << 8) |
- (this.id[1] << 16) |
- (this.id[0] << 24);
- } else {
- time =
- this.id.charCodeAt(3) |
- (this.id.charCodeAt(2) << 8) |
- (this.id.charCodeAt(1) << 16) |
- (this.id.charCodeAt(0) << 24);
- }
- timestamp.setTime(Math.floor(time) * 1000);
- return timestamp;
- }
- /**
- * Generate a 12 byte id buffer used in ObjectID's
- *
- * @method
- * @param {number} [time] optional parameter allowing to pass in a second based timestamp.
- * @return {string} return the 12 byte id buffer string.
- */
- generate(time) {
- if ("number" !== typeof time) {
- time = ~~(Date.now() / 1000);
- }
-
- //keep it in the ring!
- time = parseInt(time, 10) % 0xffffffff;
-
- var inc = next();
-
- return String.fromCharCode(
- (time >> 24) & 0xff,
- (time >> 16) & 0xff,
- (time >> 8) & 0xff,
- time & 0xff,
- (MACHINE_ID >> 16) & 0xff,
- (MACHINE_ID >> 8) & 0xff,
- MACHINE_ID & 0xff,
- (pid >> 8) & 0xff,
- pid & 0xff,
- (inc >> 16) & 0xff,
- (inc >> 8) & 0xff,
- inc & 0xff
- );
- }
-
- toJSON() {
- return this.toHexString();
- }
- toString() {
- return this.toHexString();
- }
-}
-
-export default ObjectID;
-
-function next() {
- return (index = (index + 1) % 0xffffff);
-}
-
-function hex(length, n) {
- n = n.toString(16);
- return n.length === length ? n : "00000000".substring(n.length, length) + n;
-}
diff --git a/packages/core/src/utils/object-id.ts b/packages/core/src/utils/object-id.ts
new file mode 100644
index 000000000..a9ebe4df9
--- /dev/null
+++ b/packages/core/src/utils/object-id.ts
@@ -0,0 +1,51 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import { BufferPool } from "./buffer-pool";
+import { randomBytes, randomInt } from "./random";
+
+const PROCESS_UNIQUE = randomBytes(5);
+let index = ~~(randomInt() * 0xffffff);
+
+const objectIdPool = new BufferPool(12);
+export function createObjectId(date = Date.now()): string {
+ const buffer = objectIdPool.alloc();
+ index = (index + 1) % 0xffffff;
+
+ const time = ~~(date / 1000);
+
+ // 4-byte timestamp
+ new DataView(buffer.buffer, 0, 4).setUint32(0, time);
+
+ // 5-byte process unique
+ buffer[4] = PROCESS_UNIQUE[0];
+ buffer[5] = PROCESS_UNIQUE[1];
+ buffer[6] = PROCESS_UNIQUE[2];
+ buffer[7] = PROCESS_UNIQUE[3];
+ buffer[8] = PROCESS_UNIQUE[4];
+
+ // 3-byte counter
+ buffer[11] = index & 0xff;
+ buffer[10] = (index >> 8) & 0xff;
+ buffer[9] = (index >> 16) & 0xff;
+
+ const objectId = buffer.toString("hex");
+ objectIdPool.free(buffer);
+ return objectId;
+}