TTP -> MTP, A lot of other stuff #21
6 changed files with 195 additions and 82 deletions
commit
8ddc8db536
|
|
@ -35,6 +35,10 @@ const CHAT_SECRET_VERSION = 1;
|
||||||
|
|
||||||
function bytesFromProtocol(value: unknown): Uint8Array {
|
function bytesFromProtocol(value: unknown): Uint8Array {
|
||||||
if (value instanceof Uint8Array) return value;
|
if (value instanceof Uint8Array) return value;
|
||||||
|
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
||||||
|
if (ArrayBuffer.isView(value)) {
|
||||||
|
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
||||||
|
}
|
||||||
if (Array.isArray(value)) return new Uint8Array(value);
|
if (Array.isArray(value)) return new Uint8Array(value);
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
const bin = atob(value);
|
const bin = atob(value);
|
||||||
|
|
@ -42,6 +46,16 @@ function bytesFromProtocol(value: unknown): Uint8Array {
|
||||||
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
const entries = Object.entries(value);
|
||||||
|
if (
|
||||||
|
entries.every(
|
||||||
|
([key, item]) => /^\d+$/.test(key) && typeof item === "number",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return new Uint8Array(entries.map(([, item]) => item as number));
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new Error("expected protocol bytes");
|
throw new Error("expected protocol bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,6 +86,16 @@ function updateMessageStateBySendTime<
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertProtocolSuccess(type: string, response: { type: string }) {
|
||||||
|
if (response.type.startsWith("Error")) {
|
||||||
|
throw new Error(`${type} failed: ${response.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function protocolBytes(bytes: Uint8Array): Uint8Array<ArrayBuffer> {
|
||||||
|
return new Uint8Array(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
export default function Provider({ children }: { children: ReactNode }) {
|
export default function Provider({ children }: { children: ReactNode }) {
|
||||||
const { load } = useStorage();
|
const { load } = useStorage();
|
||||||
const { send, subscribePush } = useMTP();
|
const { send, subscribePush } = useMTP();
|
||||||
|
|
@ -125,6 +149,34 @@ export default function Provider({ children }: { children: ReactNode }) {
|
||||||
const chatId = deriveChatId(ownUserId, userIdValue);
|
const chatId = deriveChatId(ownUserId, userIdValue);
|
||||||
const secretId = deriveChatSecretId(chatId);
|
const secretId = deriveChatSecretId(chatId);
|
||||||
|
|
||||||
|
async function forwardChatSecret(chatSecret: Uint8Array) {
|
||||||
|
const peerUser = await getUser(userIdValue);
|
||||||
|
const peerWrapped = await wrapChatSecret({
|
||||||
|
chatSecret,
|
||||||
|
recipientKemPublicKey: kemPublicKeyFromPublicKeyBundle(
|
||||||
|
peerUser.PublicKey,
|
||||||
|
),
|
||||||
|
chatId,
|
||||||
|
secretId,
|
||||||
|
version: CHAT_SECRET_VERSION,
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProtocolSuccess(
|
||||||
|
"ChatSecretForward",
|
||||||
|
await send("ChatSecretForward", {
|
||||||
|
ChatId: chatId,
|
||||||
|
SenderUserId: String(ownUserId),
|
||||||
|
RecipientUserId: String(userIdValue),
|
||||||
|
SecretId: secretId,
|
||||||
|
VersionNumber: CHAT_SECRET_VERSION,
|
||||||
|
EncryptedSecret: protocolBytes(peerWrapped.encryptedSecret),
|
||||||
|
KemCiphertext: protocolBytes(peerWrapped.kemCiphertext),
|
||||||
|
WrappingScheme: peerWrapped.wrappingScheme,
|
||||||
|
CreatedAt: Date.now(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const existing = await send("GetChatSecret", {
|
const existing = await send("GetChatSecret", {
|
||||||
UserId: String(ownUserId),
|
UserId: String(ownUserId),
|
||||||
ChatId: chatId,
|
ChatId: chatId,
|
||||||
|
|
@ -146,6 +198,8 @@ export default function Provider({ children }: { children: ReactNode }) {
|
||||||
if (active) {
|
if (active) {
|
||||||
setCurrentChatSecretState({ userId: userIdValue, value: secret });
|
setCurrentChatSecretState({ userId: userIdValue, value: secret });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await forwardChatSecret(secret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,39 +212,21 @@ export default function Provider({ children }: { children: ReactNode }) {
|
||||||
version: CHAT_SECRET_VERSION,
|
version: CHAT_SECRET_VERSION,
|
||||||
});
|
});
|
||||||
|
|
||||||
await send("SetChatSecret", {
|
assertProtocolSuccess(
|
||||||
UserId: String(ownUserId),
|
"SetChatSecret",
|
||||||
ChatId: chatId,
|
await send("SetChatSecret", {
|
||||||
SecretId: secretId,
|
UserId: String(ownUserId),
|
||||||
VersionNumber: CHAT_SECRET_VERSION,
|
ChatId: chatId,
|
||||||
EncryptedSecret: Array.from(ownWrapped.encryptedSecret),
|
SecretId: secretId,
|
||||||
KemCiphertext: Array.from(ownWrapped.kemCiphertext),
|
VersionNumber: CHAT_SECRET_VERSION,
|
||||||
WrappingScheme: ownWrapped.wrappingScheme,
|
EncryptedSecret: protocolBytes(ownWrapped.encryptedSecret),
|
||||||
CreatedAt: Date.now(),
|
KemCiphertext: protocolBytes(ownWrapped.kemCiphertext),
|
||||||
});
|
WrappingScheme: ownWrapped.wrappingScheme,
|
||||||
|
CreatedAt: Date.now(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const peerUser = await getUser(userIdValue);
|
await forwardChatSecret(rawSecret);
|
||||||
const peerWrapped = await wrapChatSecret({
|
|
||||||
chatSecret: rawSecret,
|
|
||||||
recipientKemPublicKey: kemPublicKeyFromPublicKeyBundle(
|
|
||||||
peerUser.PublicKey,
|
|
||||||
),
|
|
||||||
chatId,
|
|
||||||
secretId,
|
|
||||||
version: CHAT_SECRET_VERSION,
|
|
||||||
});
|
|
||||||
|
|
||||||
await send("ChatSecretForward", {
|
|
||||||
ChatId: chatId,
|
|
||||||
SenderUserId: String(ownUserId),
|
|
||||||
RecipientUserId: String(userIdValue),
|
|
||||||
SecretId: secretId,
|
|
||||||
VersionNumber: CHAT_SECRET_VERSION,
|
|
||||||
EncryptedSecret: Array.from(peerWrapped.encryptedSecret),
|
|
||||||
KemCiphertext: Array.from(peerWrapped.kemCiphertext),
|
|
||||||
WrappingScheme: peerWrapped.wrappingScheme,
|
|
||||||
CreatedAt: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
setCurrentChatSecretState({ userId: userIdValue, value: rawSecret });
|
setCurrentChatSecretState({ userId: userIdValue, value: rawSecret });
|
||||||
|
|
@ -317,6 +353,68 @@ export default function Provider({ children }: { children: ReactNode }) {
|
||||||
const keyring = await load("mtp_keyring");
|
const keyring = await load("mtp_keyring");
|
||||||
const chatId = String(data.ChatId);
|
const chatId = String(data.ChatId);
|
||||||
const secretId = String(data.SecretId);
|
const secretId = String(data.SecretId);
|
||||||
|
const senderUserId = Number(data.SenderUserId);
|
||||||
|
if (!Number.isSafeInteger(senderUserId) || senderUserId <= 0) {
|
||||||
|
throw new Error("Invalid chat secret sender id");
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await send("GetChatSecret", {
|
||||||
|
UserId: String(ownUserId),
|
||||||
|
ChatId: chatId,
|
||||||
|
SecretId: secretId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing.type.startsWith("Error") && senderUserId > ownUserId) {
|
||||||
|
const existingData = existing.data as Record<string, unknown>;
|
||||||
|
const existingSecret = await unwrapChatSecret({
|
||||||
|
encryptedSecret: bytesFromProtocol(existingData.EncryptedSecret),
|
||||||
|
kemCiphertext: bytesFromProtocol(existingData.KemCiphertext),
|
||||||
|
keyring,
|
||||||
|
chatId: String(existingData.ChatId),
|
||||||
|
secretId: String(existingData.SecretId),
|
||||||
|
version: Number(existingData.VersionNumber),
|
||||||
|
wrappingScheme: String(existingData.WrappingScheme),
|
||||||
|
});
|
||||||
|
const senderUser = await getUser(senderUserId);
|
||||||
|
const senderWrapped = await wrapChatSecret({
|
||||||
|
chatSecret: existingSecret,
|
||||||
|
recipientKemPublicKey: kemPublicKeyFromPublicKeyBundle(
|
||||||
|
senderUser.PublicKey,
|
||||||
|
),
|
||||||
|
chatId,
|
||||||
|
secretId,
|
||||||
|
version: Number(existingData.VersionNumber),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProtocolSuccess(
|
||||||
|
"ChatSecretForward",
|
||||||
|
await send("ChatSecretForward", {
|
||||||
|
ChatId: chatId,
|
||||||
|
SenderUserId: String(ownUserId),
|
||||||
|
RecipientUserId: String(senderUserId),
|
||||||
|
SecretId: String(existingData.SecretId),
|
||||||
|
VersionNumber: Number(existingData.VersionNumber),
|
||||||
|
EncryptedSecret: protocolBytes(senderWrapped.encryptedSecret),
|
||||||
|
KemCiphertext: protocolBytes(senderWrapped.kemCiphertext),
|
||||||
|
WrappingScheme: senderWrapped.wrappingScheme,
|
||||||
|
CreatedAt: Date.now(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
log(
|
||||||
|
2,
|
||||||
|
"chat",
|
||||||
|
"purple",
|
||||||
|
"Ignoring forwarded chat secret because local user owns the deterministic secret",
|
||||||
|
{
|
||||||
|
chatId,
|
||||||
|
ownUserId,
|
||||||
|
senderUserId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const secret = await unwrapChatSecret({
|
const secret = await unwrapChatSecret({
|
||||||
encryptedSecret: bytesFromProtocol(data.EncryptedSecret),
|
encryptedSecret: bytesFromProtocol(data.EncryptedSecret),
|
||||||
kemCiphertext: bytesFromProtocol(data.KemCiphertext),
|
kemCiphertext: bytesFromProtocol(data.KemCiphertext),
|
||||||
|
|
@ -334,19 +432,25 @@ export default function Provider({ children }: { children: ReactNode }) {
|
||||||
version: Number(data.VersionNumber),
|
version: Number(data.VersionNumber),
|
||||||
});
|
});
|
||||||
|
|
||||||
await send("SetChatSecret", {
|
assertProtocolSuccess(
|
||||||
UserId: String(ownUserId),
|
"SetChatSecret",
|
||||||
ChatId: chatId,
|
await send("SetChatSecret", {
|
||||||
SecretId: secretId,
|
UserId: String(ownUserId),
|
||||||
VersionNumber: Number(data.VersionNumber),
|
ChatId: chatId,
|
||||||
EncryptedSecret: Array.from(ownWrapped.encryptedSecret),
|
SecretId: secretId,
|
||||||
KemCiphertext: Array.from(ownWrapped.kemCiphertext),
|
VersionNumber: Number(data.VersionNumber),
|
||||||
WrappingScheme: ownWrapped.wrappingScheme,
|
EncryptedSecret: protocolBytes(ownWrapped.encryptedSecret),
|
||||||
CreatedAt: Date.now(),
|
KemCiphertext: protocolBytes(ownWrapped.kemCiphertext),
|
||||||
});
|
WrappingScheme: ownWrapped.wrappingScheme,
|
||||||
|
CreatedAt: Date.now(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (deriveChatId(ownUserId, userIdValue) === chatId) {
|
if (deriveChatId(ownUserId, userIdValue) === chatId) {
|
||||||
setCurrentChatSecretState({ userId: userIdValue, value: secret });
|
setCurrentChatSecretState({ userId: userIdValue, value: secret });
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: ["chat-messages", String(userIdValue)],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})().catch((err) => {
|
})().catch((err) => {
|
||||||
log(1, "chat", "red", "Failed to accept chat secret", err);
|
log(1, "chat", "red", "Failed to accept chat secret", err);
|
||||||
|
|
@ -474,6 +578,7 @@ export default function Provider({ children }: { children: ReactNode }) {
|
||||||
}, [
|
}, [
|
||||||
addLiveMessage,
|
addLiveMessage,
|
||||||
currentChatSecret,
|
currentChatSecret,
|
||||||
|
getUser,
|
||||||
load,
|
load,
|
||||||
send,
|
send,
|
||||||
subscribePush,
|
subscribePush,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { crypto } from "mtp";
|
import { base64ToBytes, bytesToBase64, crypto } from "mtp";
|
||||||
|
|
||||||
const textEncoder = new TextEncoder();
|
const textEncoder = new TextEncoder();
|
||||||
|
const textDecoder = new TextDecoder();
|
||||||
|
|
||||||
export const CHAT_SECRET_WRAPPING_SCHEME =
|
export const CHAT_SECRET_WRAPPING_SCHEME =
|
||||||
"mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1";
|
"mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1";
|
||||||
|
|
@ -73,11 +74,16 @@ export async function unwrapChatSecret(args: {
|
||||||
wrappingScheme: string;
|
wrappingScheme: string;
|
||||||
}): Promise<Uint8Array> {
|
}): Promise<Uint8Array> {
|
||||||
if (args.wrappingScheme !== CHAT_SECRET_WRAPPING_SCHEME) {
|
if (args.wrappingScheme !== CHAT_SECRET_WRAPPING_SCHEME) {
|
||||||
throw new Error(`Unsupported chat secret wrapping scheme: ${args.wrappingScheme}`);
|
throw new Error(
|
||||||
|
`Unsupported chat secret wrapping scheme: ${args.wrappingScheme}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ownKeys = crypto.keyringToKeys(args.keyring);
|
const ownKeys = crypto.keyringToKeys(args.keyring);
|
||||||
const sharedSecret = crypto.decapsulate(ownKeys.kemSecretKey, args.kemCiphertext);
|
const sharedSecret = crypto.decapsulate(
|
||||||
|
ownKeys.kemSecretKey,
|
||||||
|
args.kemCiphertext,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const wrappingKey = deriveWrappingKey({
|
const wrappingKey = deriveWrappingKey({
|
||||||
|
|
@ -103,7 +109,9 @@ export async function encryptChatText(
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const key = deriveMessageKey(chatSecret);
|
const key = deriveMessageKey(chatSecret);
|
||||||
try {
|
try {
|
||||||
return await crypto.encryptText(key, plaintext);
|
return bytesToBase64(
|
||||||
|
await crypto.encrypt(key, textEncoder.encode(plaintext)),
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
key.fill(0);
|
key.fill(0);
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +123,9 @@ export async function decryptChatText(
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const key = deriveMessageKey(chatSecret);
|
const key = deriveMessageKey(chatSecret);
|
||||||
try {
|
try {
|
||||||
return await crypto.decryptText(key, ciphertext);
|
return textDecoder.decode(
|
||||||
|
await crypto.decrypt(key, base64ToBytes(ciphertext)),
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
key.fill(0);
|
key.fill(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,9 @@ function validateResponse<T extends keyof Schemas & string>(
|
||||||
return message as ProtocolMessage<T>;
|
return message as ProtocolMessage<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = schemas[type]?.response;
|
const schema =
|
||||||
|
schemas[message.type as keyof Schemas & string]?.response ??
|
||||||
|
schemas[type]?.response;
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return message as ProtocolMessage<T>;
|
return message as ProtocolMessage<T>;
|
||||||
}
|
}
|
||||||
|
|
@ -574,15 +576,7 @@ export function Provider(props: {
|
||||||
subscribePush,
|
subscribePush,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
}, [connected, identified, mtpUrl, send, subscribe, subscribePush, mtpRef]);
|
||||||
connected,
|
|
||||||
identified,
|
|
||||||
mtpUrl,
|
|
||||||
send,
|
|
||||||
subscribe,
|
|
||||||
subscribePush,
|
|
||||||
mtpRef,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const sendQueued: BoundSendFn = useMemo(
|
const sendQueued: BoundSendFn = useMemo(
|
||||||
() => async (type, data, options) => {
|
() => async (type, data, options) => {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,20 @@ const bytesLike = z.union([
|
||||||
z.base64(),
|
z.base64(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const protocolBytes = z.instanceof(Uint8Array);
|
||||||
|
|
||||||
|
const chatSecretResponse = z.object({
|
||||||
|
UserId: z.string(),
|
||||||
|
ChatId: z.string(),
|
||||||
|
SecretId: z.string(),
|
||||||
|
VersionNumber: z.number(),
|
||||||
|
EncryptedSecret: bytesLike,
|
||||||
|
KemCiphertext: bytesLike,
|
||||||
|
WrappingScheme: z.string(),
|
||||||
|
CreatedAt: z.number(),
|
||||||
|
UpdatedAt: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
export const Message = z.object({
|
export const Message = z.object({
|
||||||
NotEncrypted: z.boolean().optional(),
|
NotEncrypted: z.boolean().optional(),
|
||||||
SentBySelf: z.boolean().optional(),
|
SentBySelf: z.boolean().optional(),
|
||||||
|
|
@ -257,8 +271,8 @@ export const mtp = {
|
||||||
ChatId: z.string(),
|
ChatId: z.string(),
|
||||||
SecretId: z.string(),
|
SecretId: z.string(),
|
||||||
VersionNumber: z.number(),
|
VersionNumber: z.number(),
|
||||||
EncryptedSecret: bytesLike,
|
EncryptedSecret: protocolBytes,
|
||||||
KemCiphertext: bytesLike,
|
KemCiphertext: protocolBytes,
|
||||||
WrappingScheme: z.string(),
|
WrappingScheme: z.string(),
|
||||||
CreatedAt: z.number(),
|
CreatedAt: z.number(),
|
||||||
}),
|
}),
|
||||||
|
|
@ -270,21 +284,11 @@ export const mtp = {
|
||||||
ChatId: z.string(),
|
ChatId: z.string(),
|
||||||
SecretId: z.string().optional(),
|
SecretId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
response: z.object({}),
|
response: chatSecretResponse,
|
||||||
},
|
},
|
||||||
ChatSecretResponse: {
|
ChatSecretResponse: {
|
||||||
request: z.object({}).optional(),
|
request: z.object({}).optional(),
|
||||||
response: z.object({
|
response: chatSecretResponse,
|
||||||
UserId: z.string(),
|
|
||||||
ChatId: z.string(),
|
|
||||||
SecretId: z.string(),
|
|
||||||
VersionNumber: z.number(),
|
|
||||||
EncryptedSecret: bytesLike,
|
|
||||||
KemCiphertext: bytesLike,
|
|
||||||
WrappingScheme: z.string(),
|
|
||||||
CreatedAt: z.number(),
|
|
||||||
UpdatedAt: z.number(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
ChatSecretForward: {
|
ChatSecretForward: {
|
||||||
request: z.object({
|
request: z.object({
|
||||||
|
|
@ -293,8 +297,8 @@ export const mtp = {
|
||||||
RecipientUserId: z.string(),
|
RecipientUserId: z.string(),
|
||||||
SecretId: z.string(),
|
SecretId: z.string(),
|
||||||
VersionNumber: z.number(),
|
VersionNumber: z.number(),
|
||||||
EncryptedSecret: bytesLike,
|
EncryptedSecret: protocolBytes,
|
||||||
KemCiphertext: bytesLike,
|
KemCiphertext: protocolBytes,
|
||||||
WrappingScheme: z.string(),
|
WrappingScheme: z.string(),
|
||||||
CreatedAt: z.number(),
|
CreatedAt: z.number(),
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
|
|
@ -6,7 +6,7 @@ settings:
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
'@tensamin/ui': https://git.methanium.net/tensamin/ui/releases/download/0.0.40/tensamin-ui.tgz
|
'@tensamin/ui': https://git.methanium.net/tensamin/ui/releases/download/0.0.40/tensamin-ui.tgz
|
||||||
mtp: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz
|
mtp: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
|
|
@ -16,8 +16,8 @@ importers:
|
||||||
specifier: https://git.methanium.net/tensamin/ui/releases/download/0.0.40/tensamin-ui.tgz
|
specifier: https://git.methanium.net/tensamin/ui/releases/download/0.0.40/tensamin-ui.tgz
|
||||||
version: https://git.methanium.net/tensamin/ui/releases/download/0.0.40/tensamin-ui.tgz(@date-fns/tz@1.5.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1)(typescript@6.0.3)
|
version: https://git.methanium.net/tensamin/ui/releases/download/0.0.40/tensamin-ui.tgz(@date-fns/tz@1.5.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1)(typescript@6.0.3)
|
||||||
mtp:
|
mtp:
|
||||||
specifier: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz
|
specifier: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz
|
||||||
version: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz
|
version: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz
|
||||||
sonner:
|
sonner:
|
||||||
specifier: ^2.0.7
|
specifier: ^2.0.7
|
||||||
version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
|
|
@ -652,8 +652,8 @@ importers:
|
||||||
specifier: ^1.14.0
|
specifier: ^1.14.0
|
||||||
version: 1.23.0(react@19.2.7)
|
version: 1.23.0(react@19.2.7)
|
||||||
mtp:
|
mtp:
|
||||||
specifier: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz
|
specifier: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz
|
||||||
version: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz
|
version: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz
|
||||||
react:
|
react:
|
||||||
specifier: ^19.2.0
|
specifier: ^19.2.0
|
||||||
version: 19.2.7
|
version: 19.2.7
|
||||||
|
|
@ -3736,8 +3736,8 @@ packages:
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
mtp@https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz:
|
mtp@https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz:
|
||||||
resolution: {integrity: sha512-Y18aXdMfM80YLBGzk3NS5PVl08GHX2Cfkw0aQK06xazA3zZlPVHStS7enZ4s4MmDzE6NNTYBYbw49nWpUgkdvQ==, tarball: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz}
|
resolution: {integrity: sha512-A2SN971Er728ibaemcL1qu1PGJuOZxNGY8ssgld3ETF94tVJ3GeBHQERcbQNdj9fExPEmnjkq/o1tt8W+Vdcvw==, tarball: https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz}
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
|
||||||
nanoid@3.3.15:
|
nanoid@3.3.15:
|
||||||
|
|
@ -7857,7 +7857,7 @@ snapshots:
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
mtp@https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz: {}
|
mtp@https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz: {}
|
||||||
|
|
||||||
nanoid@3.3.15: {}
|
nanoid@3.3.15: {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ allowBuilds:
|
||||||
esbuild: true
|
esbuild: true
|
||||||
overrides:
|
overrides:
|
||||||
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/releases/download/0.0.40/tensamin-ui.tgz"
|
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/releases/download/0.0.40/tensamin-ui.tgz"
|
||||||
mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-515244c/mtp-0.1.0.tgz"
|
mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.1.0-dev-40e9423/mtp-0.1.0.tgz"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue