Comparing version 4.1.0 to 5.0.0
@@ -20,3 +20,3 @@ "use strict"; | ||
exports.DEFAULT_CONNECTION_CONFIG = { | ||
version: [2, 2142, 12], | ||
version: [2, 2144, 11], | ||
browser: Utils_1.Browsers.baileys('Chrome'), | ||
@@ -28,3 +28,3 @@ waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat', | ||
printQRInTerminal: false, | ||
emitOwnEvents: true | ||
emitOwnEvents: true, | ||
}; | ||
@@ -31,0 +31,0 @@ exports.MEDIA_PATH_MAP = { |
/// <reference types="ws" /> | ||
/// <reference types="node" /> | ||
import { SocketConfig, WAPresence, Chat, WAPatchCreate, WAMediaUpload, WAPatchName, ChatModification, Contact } from "../Types"; | ||
import { SocketConfig, WAPresence, WAPatchCreate, WAMediaUpload, WAPatchName, ChatModification, Contact } from "../Types"; | ||
import { BinaryNode } from "../WABinary"; | ||
@@ -22,8 +22,5 @@ import { proto } from '../../WAProto'; | ||
updateBlockStatus: (jid: string, action: 'block' | 'unblock') => Promise<void>; | ||
resyncAppState: (collections: WAPatchName[], fromScratch?: boolean, returnSnapshot?: boolean) => Promise<void>; | ||
resyncAppState: (collections: WAPatchName[], returnSnapshot?: boolean) => Promise<void>; | ||
chatModify: (mod: ChatModification, jid: string, lastMessages: Pick<proto.IWebMessageInfo, 'key' | 'messageTimestamp'>[]) => Promise<void>; | ||
processMessage: (message: proto.IWebMessageInfo, chatUpdate: Partial<Chat>) => Promise<void>; | ||
sendMessageAck: ({ tag, attrs }: BinaryNode, extraAttrs: { | ||
[key: string]: string; | ||
}) => Promise<void>; | ||
resyncMainAppState: () => Promise<void>; | ||
assertSession: (jid: string, force: boolean) => Promise<boolean>; | ||
@@ -34,2 +31,3 @@ relayMessage: (jid: string, message: proto.IMessage, { messageId: msgId, additionalAttributes, cachedGroupMetadata }: import("../Types").MessageRelayOptions) => Promise<string>; | ||
refreshMediaConn: (forceGet?: boolean) => Promise<import("../Types").MediaConnInfo>; | ||
waUploadToServer: import("../Types").WAMediaUploadFunction; | ||
fetchPrivacySettings: (force?: boolean) => Promise<{ | ||
@@ -41,3 +39,3 @@ [_: string]: string; | ||
groupCreate: (subject: string, participants: string[]) => Promise<import("../Types").GroupMetadata>; | ||
groupLeave: (jid: string) => Promise<void>; | ||
groupLeave: (id: string) => Promise<void>; | ||
groupUpdateSubject: (jid: string, subject: string) => Promise<void>; | ||
@@ -47,2 +45,4 @@ groupParticipantsUpdate: (jid: string, participants: string[], action: import("../Types").ParticipantAction) => Promise<string[]>; | ||
groupInviteCode: (jid: string) => Promise<string>; | ||
groupRevokeInvite: (jid: string) => Promise<string>; | ||
groupAcceptInvite: (code: string) => Promise<string>; | ||
groupToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>; | ||
@@ -49,0 +49,0 @@ groupSettingUpdate: (jid: string, setting: "announcement" | "not_announcement" | "locked" | "unlocked") => Promise<void>; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -7,7 +10,9 @@ exports.makeChatsSocket = void 0; | ||
const Utils_1 = require("../Utils"); | ||
const messages_recv_1 = require("./messages-recv"); | ||
const messages_send_1 = require("./messages-send"); | ||
const make_mutex_1 = __importDefault(require("../Utils/make-mutex")); | ||
const makeChatsSocket = (config) => { | ||
const { logger } = config; | ||
const sock = messages_recv_1.makeMessagesRecvSocket(config); | ||
const sock = messages_send_1.makeMessagesSocket(config); | ||
const { ev, ws, authState, generateMessageTag, sendNode, query, fetchPrivacySettings, } = sock; | ||
const mutationMutex = make_mutex_1.default(); | ||
const interactiveQuery = async (userNodes, queryNode) => { | ||
@@ -145,3 +150,5 @@ const result = await query({ | ||
}; | ||
const resyncAppState = async (collections, fromScratch = false, returnSnapshot = false) => { | ||
const resyncAppStateInternal = async (collections, fromScratch = false, returnSnapshot = false) => { | ||
if (fromScratch) | ||
returnSnapshot = true; | ||
const states = {}; | ||
@@ -151,3 +158,3 @@ for (const name of collections) { | ||
if (!state) | ||
state = { version: 0, hash: Buffer.alloc(128), mutations: [] }; | ||
state = Utils_1.newLTHashState(); | ||
states[name] = state; | ||
@@ -178,8 +185,14 @@ logger.info(`resyncing ${name} from v${state.version}`); | ||
}); | ||
const decoded = Utils_1.extractSyncdPatches(result); // extract from binary node | ||
const decoded = await Utils_1.extractSyncdPatches(result); // extract from binary node | ||
for (const key in decoded) { | ||
const name = key; | ||
const { patches, snapshot } = decoded[name]; | ||
if (snapshot) { | ||
const newState = await Utils_1.decodeSyncdSnapshot(name, snapshot, authState.keys.getAppStateSyncKey); | ||
states[name] = newState; | ||
logger.info(`restored state of ${name} from snapshot to v${newState.version}`); | ||
} | ||
// only process if there are syncd patches | ||
if (decoded[name].length) { | ||
const { newMutations, state: newState } = await Utils_1.decodePatches(name, decoded[name], states[name], authState, true); | ||
if (patches.length) { | ||
const { newMutations, state: newState } = await Utils_1.decodePatches(name, patches, states[name], authState.keys.getAppStateSyncKey, true); | ||
await authState.keys.setAppStateSyncVersion(name, newState); | ||
@@ -190,4 +203,12 @@ logger.info(`synced ${name} to v${newState.version}`); | ||
} | ||
ev.emit('auth-state.update', authState); | ||
}; | ||
const resyncAppState = async (collections, returnSnapshot = false) => { | ||
try { | ||
await resyncAppStateInternal(collections, returnSnapshot); | ||
} | ||
catch (error) { | ||
logger.info({ collections, error: error.stack }, 'failed to sync state from version, trying from scratch'); | ||
await resyncAppStateInternal(collections, true, true); | ||
} | ||
}; | ||
/** | ||
@@ -216,2 +237,3 @@ * fetch the profile picture of a user/group | ||
const sendPresenceUpdate = async (type, toJid) => { | ||
const me = authState.creds.me; | ||
if (type === 'available' || type === 'unavailable') { | ||
@@ -221,3 +243,3 @@ await sendNode({ | ||
attrs: { | ||
name: authState.creds.me.name, | ||
name: me.name, | ||
type | ||
@@ -231,3 +253,3 @@ } | ||
attrs: { | ||
from: authState.creds.me.id, | ||
from: me.id, | ||
to: toJid, | ||
@@ -274,2 +296,13 @@ }, | ||
}; | ||
const resyncMainAppState = async () => { | ||
logger.debug('resyncing main app state'); | ||
await (mutationMutex.mutex(() => resyncAppState([ | ||
'critical_block', | ||
'critical_unblock_low', | ||
'regular_high', | ||
'regular_low', | ||
'regular' | ||
])) | ||
.catch(err => (logger.warn({ trace: err.stack }, 'failed to sync app state')))); | ||
}; | ||
const processSyncActions = (actions) => { | ||
@@ -308,4 +341,7 @@ var _a, _b, _c, _d; | ||
else if (action === null || action === void 0 ? void 0 : action.pushNameSetting) { | ||
authState.creds.me.name = (_d = action === null || action === void 0 ? void 0 : action.pushNameSetting) === null || _d === void 0 ? void 0 : _d.name; | ||
ev.emit('auth-state.update', authState); | ||
const me = { | ||
...authState.creds.me, | ||
name: (_d = action === null || action === void 0 ? void 0 : action.pushNameSetting) === null || _d === void 0 ? void 0 : _d.name | ||
}; | ||
ev.emit('creds.update', { me }); | ||
} | ||
@@ -334,50 +370,45 @@ else { | ||
const name = patchCreate.type; | ||
try { | ||
await mutationMutex.mutex(async () => { | ||
await resyncAppState([name]); | ||
} | ||
catch (error) { | ||
logger.info({ name, error: error.stack }, 'failed to sync state from version, trying from scratch'); | ||
await resyncAppState([name], true); | ||
} | ||
const { patch, state } = await Utils_1.encodeSyncdPatch(patchCreate, authState); | ||
const initial = await authState.keys.getAppStateSyncVersion(name); | ||
// temp: verify it was encoded correctly | ||
const result = await Utils_1.decodePatches(name, [{ ...patch, version: { version: state.version }, }], initial, authState); | ||
const node = { | ||
tag: 'iq', | ||
attrs: { | ||
to: WABinary_1.S_WHATSAPP_NET, | ||
type: 'set', | ||
xmlns: 'w:sync:app:state' | ||
}, | ||
content: [ | ||
{ | ||
tag: 'sync', | ||
attrs: {}, | ||
content: [ | ||
{ | ||
tag: 'collection', | ||
attrs: { | ||
name, | ||
version: (state.version - 1).toString(), | ||
return_snapshot: 'false' | ||
}, | ||
content: [ | ||
{ | ||
tag: 'patch', | ||
attrs: {}, | ||
content: WAProto_1.proto.SyncdPatch.encode(patch).finish() | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
}; | ||
await query(node); | ||
await authState.keys.setAppStateSyncVersion(name, state); | ||
ev.emit('auth-state.update', authState); | ||
if (config.emitOwnEvents) { | ||
processSyncActions(result.newMutations); | ||
} | ||
const { patch, state } = await Utils_1.encodeSyncdPatch(patchCreate, authState); | ||
const initial = await authState.keys.getAppStateSyncVersion(name); | ||
// temp: verify it was encoded correctly | ||
const result = await Utils_1.decodePatches(name, [{ ...patch, version: { version: state.version }, }], initial, authState.keys.getAppStateSyncKey); | ||
const node = { | ||
tag: 'iq', | ||
attrs: { | ||
to: WABinary_1.S_WHATSAPP_NET, | ||
type: 'set', | ||
xmlns: 'w:sync:app:state' | ||
}, | ||
content: [ | ||
{ | ||
tag: 'sync', | ||
attrs: {}, | ||
content: [ | ||
{ | ||
tag: 'collection', | ||
attrs: { | ||
name, | ||
version: (state.version - 1).toString(), | ||
return_snapshot: 'false' | ||
}, | ||
content: [ | ||
{ | ||
tag: 'patch', | ||
attrs: {}, | ||
content: WAProto_1.proto.SyncdPatch.encode(patch).finish() | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
}; | ||
await query(node); | ||
await authState.keys.setAppStateSyncVersion(name, state); | ||
if (config.emitOwnEvents) { | ||
processSyncActions(result.newMutations); | ||
} | ||
}); | ||
}; | ||
@@ -406,4 +437,6 @@ const chatModify = (mod, jid, lastMessages) => { | ||
const name = update.attrs.name; | ||
resyncAppState([name], false) | ||
.catch(err => logger.error({ trace: err.stack, node }, `failed to sync state`)); | ||
mutationMutex.mutex(async () => { | ||
await resyncAppState([name], false) | ||
.catch(err => logger.error({ trace: err.stack, node }, `failed to sync state`)); | ||
}); | ||
} | ||
@@ -416,4 +449,2 @@ }); | ||
fetchPrivacySettings(); | ||
resyncAppState(['critical_block', 'critical_unblock_low']) | ||
.catch(err => logger.info({ trace: err.stack }, 'failed to sync app state')); | ||
} | ||
@@ -434,4 +465,5 @@ }); | ||
chatModify, | ||
resyncMainAppState, | ||
}; | ||
}; | ||
exports.makeChatsSocket = makeChatsSocket; |
@@ -8,3 +8,3 @@ /// <reference types="ws" /> | ||
groupCreate: (subject: string, participants: string[]) => Promise<GroupMetadata>; | ||
groupLeave: (jid: string) => Promise<void>; | ||
groupLeave: (id: string) => Promise<void>; | ||
groupUpdateSubject: (jid: string, subject: string) => Promise<void>; | ||
@@ -14,2 +14,4 @@ groupParticipantsUpdate: (jid: string, participants: string[], action: ParticipantAction) => Promise<string[]>; | ||
groupInviteCode: (jid: string) => Promise<string>; | ||
groupRevokeInvite: (jid: string) => Promise<string>; | ||
groupAcceptInvite: (code: string) => Promise<string>; | ||
groupToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>; | ||
@@ -16,0 +18,0 @@ groupSettingUpdate: (jid: string, setting: 'announcement' | 'not_announcement' | 'locked' | 'unlocked') => Promise<void>; |
@@ -43,3 +43,3 @@ "use strict"; | ||
}, | ||
groupLeave: async (jid) => { | ||
groupLeave: async (id) => { | ||
await groupQuery('@g.us', 'set', [ | ||
@@ -50,3 +50,3 @@ { | ||
content: [ | ||
{ tag: 'group', attrs: { jid } } | ||
{ tag: 'group', attrs: { id } } | ||
] | ||
@@ -95,2 +95,12 @@ } | ||
}, | ||
groupRevokeInvite: async (jid) => { | ||
const result = await groupQuery(jid, 'set', [{ tag: 'invite', attrs: {} }]); | ||
const inviteNode = WABinary_1.getBinaryNodeChild(result, 'invite'); | ||
return inviteNode.attrs.code; | ||
}, | ||
groupAcceptInvite: async (code) => { | ||
const results = await groupQuery('@g.us', 'set', [{ tag: 'invite', attrs: { code } }]); | ||
const result = WABinary_1.getBinaryNodeChild(results, 'group'); | ||
return result.attrs.jid; | ||
}, | ||
groupToggleEphemeral: async (jid, ephemeralExpiration) => { | ||
@@ -97,0 +107,0 @@ const content = ephemeralExpiration ? |
@@ -5,2 +5,6 @@ /// <reference types="ws" /> | ||
declare const makeWASocket: (config: Partial<SocketConfig>) => { | ||
processMessage: (message: import("../Types").WAProto.IWebMessageInfo, chatUpdate: Partial<import("../Types").Chat>) => Promise<void>; | ||
sendMessageAck: ({ tag, attrs }: import("..").BinaryNode, extraAttrs: { | ||
[key: string]: string; | ||
}) => Promise<void>; | ||
appPatch: (patchCreate: import("../Types").WAPatchCreate) => Promise<void>; | ||
@@ -21,8 +25,5 @@ sendPresenceUpdate: (type: import("../Types").WAPresence, toJid?: string) => Promise<void>; | ||
updateBlockStatus: (jid: string, action: "block" | "unblock") => Promise<void>; | ||
resyncAppState: (collections: import("../Types").WAPatchName[], fromScratch?: boolean, returnSnapshot?: boolean) => Promise<void>; | ||
resyncAppState: (collections: import("../Types").WAPatchName[], returnSnapshot?: boolean) => Promise<void>; | ||
chatModify: (mod: import("../Types").ChatModification, jid: string, lastMessages: Pick<import("../Types").WAProto.IWebMessageInfo, "key" | "messageTimestamp">[]) => Promise<void>; | ||
processMessage: (message: import("../Types").WAProto.IWebMessageInfo, chatUpdate: Partial<import("../Types").Chat>) => Promise<void>; | ||
sendMessageAck: ({ tag, attrs }: import("..").BinaryNode, extraAttrs: { | ||
[key: string]: string; | ||
}) => Promise<void>; | ||
resyncMainAppState: () => Promise<void>; | ||
assertSession: (jid: string, force: boolean) => Promise<boolean>; | ||
@@ -33,2 +34,3 @@ relayMessage: (jid: string, message: import("../Types").WAProto.IMessage, { messageId: msgId, additionalAttributes, cachedGroupMetadata }: import("../Types").MessageRelayOptions) => Promise<string>; | ||
refreshMediaConn: (forceGet?: boolean) => Promise<import("../Types").MediaConnInfo>; | ||
waUploadToServer: import("../Types").WAMediaUploadFunction; | ||
fetchPrivacySettings: (force?: boolean) => Promise<{ | ||
@@ -40,3 +42,3 @@ [_: string]: string; | ||
groupCreate: (subject: string, participants: string[]) => Promise<import("../Types").GroupMetadata>; | ||
groupLeave: (jid: string) => Promise<void>; | ||
groupLeave: (id: string) => Promise<void>; | ||
groupUpdateSubject: (jid: string, subject: string) => Promise<void>; | ||
@@ -46,2 +48,4 @@ groupParticipantsUpdate: (jid: string, participants: string[], action: import("../Types").ParticipantAction) => Promise<string[]>; | ||
groupInviteCode: (jid: string) => Promise<string>; | ||
groupRevokeInvite: (jid: string) => Promise<string>; | ||
groupAcceptInvite: (code: string) => Promise<string>; | ||
groupToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>; | ||
@@ -48,0 +52,0 @@ groupSettingUpdate: (jid: string, setting: "announcement" | "not_announcement" | "locked" | "unlocked") => Promise<void>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Defaults_1 = require("../Defaults"); | ||
const chats_1 = require("./chats"); | ||
const messages_recv_1 = require("./messages-recv"); | ||
// export the last socket layer | ||
const makeWASocket = (config) => (chats_1.makeChatsSocket({ | ||
const makeWASocket = (config) => (messages_recv_1.makeMessagesRecvSocket({ | ||
...Defaults_1.DEFAULT_CONNECTION_CONFIG, | ||
@@ -8,0 +8,0 @@ ...config |
@@ -9,2 +9,20 @@ /// <reference types="ws" /> | ||
sendMessageAck: ({ tag, attrs }: BinaryNode, extraAttrs: BinaryNodeAttributes) => Promise<void>; | ||
appPatch: (patchCreate: import("../Types").WAPatchCreate) => Promise<void>; | ||
sendPresenceUpdate: (type: import("../Types").WAPresence, toJid?: string) => Promise<void>; | ||
presenceSubscribe: (toJid: string) => Promise<void>; | ||
profilePictureUrl: (jid: string, type?: "image" | "preview") => Promise<string>; | ||
onWhatsApp: (...jids: string[]) => Promise<{ | ||
exists: boolean; | ||
jid: string; | ||
}[]>; | ||
fetchBlocklist: () => Promise<void>; | ||
fetchStatus: (jid: string) => Promise<{ | ||
status: string; | ||
setAt: Date; | ||
}>; | ||
updateProfilePicture: (jid: string, content: import("../Types").WAMediaUpload) => Promise<void>; | ||
updateBlockStatus: (jid: string, action: "block" | "unblock") => Promise<void>; | ||
resyncAppState: (collections: import("../Types").WAPatchName[], returnSnapshot?: boolean) => Promise<void>; | ||
chatModify: (mod: import("../Types").ChatModification, jid: string, lastMessages: Pick<proto.IWebMessageInfo, "key" | "messageTimestamp">[]) => Promise<void>; | ||
resyncMainAppState: () => Promise<void>; | ||
assertSession: (jid: string, force: boolean) => Promise<boolean>; | ||
@@ -15,2 +33,3 @@ relayMessage: (jid: string, message: proto.IMessage, { messageId: msgId, additionalAttributes, cachedGroupMetadata }: import("../Types").MessageRelayOptions) => Promise<string>; | ||
refreshMediaConn: (forceGet?: boolean) => Promise<import("../Types").MediaConnInfo>; | ||
waUploadToServer: import("../Types").WAMediaUploadFunction; | ||
fetchPrivacySettings: (force?: boolean) => Promise<{ | ||
@@ -22,3 +41,3 @@ [_: string]: string; | ||
groupCreate: (subject: string, participants: string[]) => Promise<GroupMetadata>; | ||
groupLeave: (jid: string) => Promise<void>; | ||
groupLeave: (id: string) => Promise<void>; | ||
groupUpdateSubject: (jid: string, subject: string) => Promise<void>; | ||
@@ -28,2 +47,4 @@ groupParticipantsUpdate: (jid: string, participants: string[], action: ParticipantAction) => Promise<string[]>; | ||
groupInviteCode: (jid: string) => Promise<string>; | ||
groupRevokeInvite: (jid: string) => Promise<string>; | ||
groupAcceptInvite: (code: string) => Promise<string>; | ||
groupToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>; | ||
@@ -30,0 +51,0 @@ groupSettingUpdate: (jid: string, setting: "announcement" | "not_announcement" | "locked" | "unlocked") => Promise<void>; |
@@ -9,9 +9,10 @@ "use strict"; | ||
const Defaults_1 = require("../Defaults"); | ||
const messages_send_1 = require("./messages-send"); | ||
const chats_1 = require("./chats"); | ||
const groups_1 = require("./groups"); | ||
const CALL_TAGS_TO_ACK = ['terminate', 'relaylatency', 'offer']; | ||
const isReadReceipt = (type) => type === 'read' || type === 'read-self'; | ||
const makeMessagesRecvSocket = (config) => { | ||
const { logger } = config; | ||
const sock = messages_send_1.makeMessagesSocket(config); | ||
const { ev, authState, ws, assertingPreKeys, sendNode, relayMessage, sendDeliveryReceipt, } = sock; | ||
const sock = chats_1.makeChatsSocket(config); | ||
const { ev, authState, ws, assertingPreKeys, sendNode, relayMessage, sendDeliveryReceipt, resyncMainAppState, } = sock; | ||
const msgRetryMap = config.msgRetryCounterMap || {}; | ||
@@ -28,3 +29,3 @@ const sendMessageAck = async ({ tag, attrs }, extraAttrs) => { | ||
if (!!attrs.participant) { | ||
stanza.attrs.participant = WABinary_1.jidNormalizedUser(attrs.participant); | ||
stanza.attrs.participant = attrs.participant; | ||
} | ||
@@ -139,2 +140,3 @@ logger.debug({ recv: attrs, sent: stanza.attrs }, `sent "${tag}" ack`); | ||
case WAProto_1.proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_SHARE: | ||
let newAppStateSyncKeyId = ''; | ||
for (const { keyData, keyId } of protocolMsg.appStateSyncKeyShare.keys || []) { | ||
@@ -144,5 +146,6 @@ const str = Buffer.from(keyId.keyId).toString('base64'); | ||
await authState.keys.setAppStateSyncKey(str, keyData); | ||
authState.creds.myAppStateKeyId = str; | ||
newAppStateSyncKeyId = str; | ||
} | ||
ev.emit('auth-state.update', authState); | ||
ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId }); | ||
resyncMainAppState(); | ||
break; | ||
@@ -230,3 +233,5 @@ case WAProto_1.proto.ProtocolMessage.ProtocolMessageType.REVOKE: | ||
} | ||
ev.emit('messages.upsert', { messages, type: 'prepend' }); | ||
if (messages.length) { | ||
ev.emit('messages.upsert', { messages, type: 'prepend' }); | ||
} | ||
break; | ||
@@ -251,2 +256,5 @@ case WAProto_1.proto.HistorySync.HistorySyncHistorySyncType.PUSH_NAME: | ||
result.messageStubParameters = [metadata.subject]; | ||
result.key = { | ||
participant: WABinary_1.jidNormalizedUser(metadata.owner) | ||
}; | ||
ev.emit('chats.upsert', [{ | ||
@@ -275,3 +283,11 @@ id: metadata.id, | ||
result.messageStubType = Types_1.WAMessageStubType[stubType]; | ||
result.messageStubParameters = WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid); | ||
const participants = WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid); | ||
if (participants.length === 1 && | ||
// if recv. "remove" message and sender removed themselves | ||
// mark as left | ||
WABinary_1.areJidsSameUser(participants[0], node.attrs.participant) && | ||
child.tag === 'remove') { | ||
result.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_LEAVE; | ||
} | ||
result.messageStubParameters = participants; | ||
break; | ||
@@ -296,8 +312,2 @@ case 'subject': | ||
switch (child.tag) { | ||
case 'count': | ||
if (child.attrs.value === '0') { | ||
logger.info('recv all pending notifications'); | ||
ev.emit('connection.update', { receivedPendingNotifications: true }); | ||
} | ||
break; | ||
case 'devices': | ||
@@ -320,5 +330,2 @@ const devices = WABinary_1.getBinaryNodeChildren(child, 'device'); | ||
const dec = await Utils_1.decodeMessageStanza(stanza, authState); | ||
if (dec.successes.length) { | ||
ev.emit('auth-state.update', authState); | ||
} | ||
const fullMessages = []; | ||
@@ -336,3 +343,8 @@ const { attrs } = stanza; | ||
}; | ||
for (const msg of dec.successes) { | ||
const partialMsg = { | ||
messageTimestamp: dec.timestamp, | ||
pushName: dec.pushname | ||
}; | ||
// if there were some successful decryptions | ||
if (dec.successes.length) { | ||
// send message receipt | ||
@@ -371,2 +383,4 @@ let recpAttrs; | ||
logger.debug({ msgId: dec.msgId }, 'sent delivery receipt'); | ||
} | ||
for (const msg of dec.successes) { | ||
const message = ((_b = msg.deviceSentMessage) === null || _b === void 0 ? void 0 : _b.message) || msg; | ||
@@ -377,5 +391,3 @@ fullMessages.push({ | ||
status: isMe ? WAProto_1.proto.WebMessageInfo.WebMessageInfoStatus.SERVER_ACK : null, | ||
messageTimestamp: dec.timestamp, | ||
pushName: dec.pushname, | ||
participant: dec.participant | ||
...partialMsg | ||
}); | ||
@@ -389,3 +401,4 @@ } | ||
messageStubType: Types_1.WAMessageStubType.CIPHERTEXT, | ||
messageStubParameters: [error.message] | ||
messageStubParameters: [error.message], | ||
...partialMsg | ||
}); | ||
@@ -399,2 +412,5 @@ } | ||
} | ||
else { | ||
logger.warn({ stanza }, `received node with 0 messages`); | ||
} | ||
}); | ||
@@ -415,3 +431,3 @@ ws.on('CB:ack,class:message', async (node) => { | ||
const [child] = WABinary_1.getAllBinaryNodeChildren(node); | ||
if (child.tag === 'terminate' || child.tag === 'relaylatency') { | ||
if (CALL_TAGS_TO_ACK.includes(child.tag)) { | ||
await sendMessageAck(node, { class: 'call', type: child.tag }); | ||
@@ -452,3 +468,4 @@ } | ||
participant: node.attrs.participant, | ||
id: node.attrs.id | ||
id: node.attrs.id, | ||
...(msg.key || {}) | ||
}; | ||
@@ -470,4 +487,3 @@ msg.messageTimestamp = +node.attrs.t; | ||
if (msg.key.fromMe && ((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.name) !== msg.pushName) { | ||
authState.creds.me.name = msg.pushName; | ||
ev.emit('auth-state.update', authState); | ||
ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } }); | ||
} | ||
@@ -474,0 +490,0 @@ } |
/// <reference types="ws" /> | ||
/// <reference types="node" /> | ||
import { SocketConfig, MediaConnInfo, AnyMessageContent, MiscMessageGenerationOptions, MessageRelayOptions } from "../Types"; | ||
import { SocketConfig, MediaConnInfo, AnyMessageContent, MiscMessageGenerationOptions, WAMediaUploadFunction, MessageRelayOptions } from "../Types"; | ||
import { BinaryNode } from '../WABinary'; | ||
@@ -12,2 +12,3 @@ import { proto } from "../../WAProto"; | ||
refreshMediaConn: (forceGet?: boolean) => Promise<MediaConnInfo>; | ||
waUploadToServer: WAMediaUploadFunction; | ||
fetchPrivacySettings: (force?: boolean) => Promise<{ | ||
@@ -19,3 +20,3 @@ [_: string]: string; | ||
groupCreate: (subject: string, participants: string[]) => Promise<import("../Types").GroupMetadata>; | ||
groupLeave: (jid: string) => Promise<void>; | ||
groupLeave: (id: string) => Promise<void>; | ||
groupUpdateSubject: (jid: string, subject: string) => Promise<void>; | ||
@@ -25,2 +26,4 @@ groupParticipantsUpdate: (jid: string, participants: string[], action: import("../Types").ParticipantAction) => Promise<string[]>; | ||
groupInviteCode: (jid: string) => Promise<string>; | ||
groupRevokeInvite: (jid: string) => Promise<string>; | ||
groupAcceptInvite: (code: string) => Promise<string>; | ||
groupToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>; | ||
@@ -27,0 +30,0 @@ groupSettingUpdate: (jid: string, setting: "announcement" | "not_announcement" | "locked" | "unlocked") => Promise<void>; |
@@ -227,3 +227,3 @@ "use strict"; | ||
if (isGroup) { | ||
const { ciphertext, senderKeyDistributionMessageKey } = await Utils_1.encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, authState); | ||
const { ciphertext, senderKeyDistributionMessageKey } = await Utils_1.encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, authState.creds.me.id, authState); | ||
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined; | ||
@@ -315,3 +315,2 @@ if (!groupData) | ||
await sendNode(stanza); | ||
ev.emit('auth-state.update', authState); | ||
return msgId; | ||
@@ -364,2 +363,3 @@ }; | ||
refreshMediaConn, | ||
waUploadToServer, | ||
fetchPrivacySettings, | ||
@@ -366,0 +366,0 @@ sendMessage: async (jid, content, options = {}) => { |
@@ -57,2 +57,3 @@ "use strict"; | ||
ws.setMaxListeners(0); | ||
const ev = new events_1.default(); | ||
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */ | ||
@@ -62,5 +63,12 @@ const ephemeralKeyPair = Utils_1.Curve.generateKeyPair(); | ||
const noise = Utils_1.makeNoiseHandler(ephemeralKeyPair); | ||
const authState = initialAuthState || Utils_1.initAuthState(); | ||
let authState = initialAuthState; | ||
if (!authState) { | ||
authState = Utils_1.useSingleFileAuthState('./auth-info-multi.json').state; | ||
logger.warn(` | ||
Baileys just created a single file state for your credentials. | ||
This will not be supported soon. | ||
Please pass the credentials in the config itself | ||
`); | ||
} | ||
const { creds } = authState; | ||
const ev = new events_1.default(); | ||
let lastDateRecv; | ||
@@ -177,10 +185,14 @@ let epoch = 0; | ||
const assertingPreKeys = async (range, execute) => { | ||
const { newPreKeys, lastPreKeyId, preKeysRange } = Utils_1.generateOrGetPreKeys(authState, range); | ||
creds.serverHasPreKeys = true; | ||
creds.nextPreKeyId = Math.max(lastPreKeyId + 1, creds.nextPreKeyId); | ||
creds.firstUnuploadedPreKeyId = Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId + 1); | ||
const { newPreKeys, lastPreKeyId, preKeysRange } = Utils_1.generateOrGetPreKeys(authState.creds, range); | ||
const update = { | ||
nextPreKeyId: Math.max(lastPreKeyId + 1, creds.nextPreKeyId), | ||
firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId + 1) | ||
}; | ||
if (!creds.serverHasPreKeys) { | ||
update.serverHasPreKeys = true; | ||
} | ||
await Promise.all(Object.keys(newPreKeys).map(k => authState.keys.setPreKey(+k, newPreKeys[+k]))); | ||
const preKeys = await Utils_1.getPreKeys(authState.keys, preKeysRange[0], preKeysRange[0] + preKeysRange[1]); | ||
await execute(preKeys); | ||
ev.emit('auth-state.update', authState); | ||
ev.emit('creds.update', update); | ||
}; | ||
@@ -244,2 +256,3 @@ /** generates and uploads a set of pre-keys */ | ||
clearInterval(keepAliveReq); | ||
clearInterval(qrTimer); | ||
ws.removeAllListeners('close'); | ||
@@ -262,3 +275,3 @@ ws.removeAllListeners('error'); | ||
}); | ||
ws.removeAllListeners('connection.update'); | ||
ev.removeAllListeners('connection.update'); | ||
}; | ||
@@ -334,20 +347,24 @@ const waitForSocketOpen = async () => { | ||
const logout = async () => { | ||
await sendNode({ | ||
tag: 'iq', | ||
attrs: { | ||
to: WABinary_1.S_WHATSAPP_NET, | ||
type: 'set', | ||
id: generateMessageTag(), | ||
xmlns: 'md' | ||
}, | ||
content: [ | ||
{ | ||
tag: 'remove-companion-device', | ||
attrs: { | ||
jid: authState.creds.me.id, | ||
reason: 'user_initiated' | ||
var _a; | ||
const jid = (_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id; | ||
if (jid) { | ||
await sendNode({ | ||
tag: 'iq', | ||
attrs: { | ||
to: WABinary_1.S_WHATSAPP_NET, | ||
type: 'set', | ||
id: generateMessageTag(), | ||
xmlns: 'md' | ||
}, | ||
content: [ | ||
{ | ||
tag: 'remove-companion-device', | ||
attrs: { | ||
jid: jid, | ||
reason: 'user_initiated' | ||
} | ||
} | ||
} | ||
] | ||
}); | ||
] | ||
}); | ||
} | ||
end(new boom_1.Boom('Intentional Logout', { statusCode: Types_1.DisconnectReason.loggedOut })); | ||
@@ -417,9 +434,2 @@ }; | ||
genPairQR(); | ||
const checkConnection = ({ connection }) => { | ||
if (connection === 'open' || connection === 'close') { | ||
clearTimeout(qrTimer); | ||
ev.off('connection.update', checkConnection); | ||
} | ||
}; | ||
ev.on('connection.update', checkConnection); | ||
}); | ||
@@ -442,5 +452,4 @@ // device paired for the first time | ||
} | ||
Object.assign(creds, updatedCreds); | ||
logger.info({ jid: creds.me.id }, 'registered connection, restart server'); | ||
ev.emit('auth-state.update', authState); | ||
logger.info({ jid: updatedCreds.me.id }, 'registered connection, restart server'); | ||
ev.emit('creds.update', updatedCreds); | ||
ev.emit('connection.update', { isNewLogin: true, qr: undefined }); | ||
@@ -461,4 +470,11 @@ end(new boom_1.Boom('Restart Required', { statusCode: Types_1.DisconnectReason.restartRequired })); | ||
logger.info('opened connection to WA'); | ||
clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try | ||
ev.emit('connection.update', { connection: 'open' }); | ||
}); | ||
ws.on('CB:ib,,offline', (node) => { | ||
const child = WABinary_1.getBinaryNodeChild(node, 'offline'); | ||
const offlineCount = +child.attrs.count; | ||
logger.info(`got ${offlineCount} offline messages/notifications`); | ||
ev.emit('connection.update', { receivedPendingNotifications: true }); | ||
}); | ||
ws.on('CB:stream:error', (node) => { | ||
@@ -474,5 +490,10 @@ logger.error({ error: node }, `stream errored out`); | ||
}); | ||
ws.on('CB:ib,,downgrade_webclient', () => { | ||
end(new boom_1.Boom('Multi-device beta not joined', { statusCode: Types_1.DisconnectReason.notJoinedBeta })); | ||
}); | ||
process.nextTick(() => { | ||
ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined }); | ||
}); | ||
// update credentials when required | ||
ev.on('creds.update', update => Object.assign(creds, update)); | ||
return { | ||
@@ -479,0 +500,0 @@ ws, |
/// <reference types="node" /> | ||
import type { Contact } from "./Contact"; | ||
import type { proto } from "../../WAProto"; | ||
import type { WAPatchName, ChatMutation } from "./Chat"; | ||
import type { WAPatchName } from "./Chat"; | ||
export declare type KeyPair = { | ||
@@ -25,12 +25,18 @@ public: Uint8Array; | ||
hash: Buffer; | ||
mutations: ChatMutation[]; | ||
indexValueMap: { | ||
[indexMacBase64: string]: { | ||
valueMac: Uint8Array | Buffer; | ||
}; | ||
}; | ||
}; | ||
export declare type AuthenticationCreds = { | ||
noiseKey: KeyPair; | ||
signedIdentityKey: KeyPair; | ||
signedPreKey: SignedKeyPair; | ||
registrationId: number; | ||
advSecretKey: string; | ||
export declare type SignalCreds = { | ||
readonly signedIdentityKey: KeyPair; | ||
readonly signedPreKey: SignedKeyPair; | ||
readonly registrationId: number; | ||
}; | ||
export declare type AuthenticationCreds = SignalCreds & { | ||
readonly noiseKey: KeyPair; | ||
readonly advSecretKey: string; | ||
me?: Contact; | ||
account?: proto.ADVSignedDeviceIdentity; | ||
account?: proto.IADVSignedDeviceIdentity; | ||
signalIdentities?: SignalIdentity[]; | ||
@@ -55,2 +61,6 @@ myAppStateKeyId?: string; | ||
}; | ||
export declare type SignalAuthState = { | ||
creds: SignalCreds; | ||
keys: SignalKeyStore; | ||
}; | ||
export declare type AuthenticationState = { | ||
@@ -57,0 +67,0 @@ creds: AuthenticationCreds; |
@@ -13,3 +13,3 @@ /// <reference types="node" /> | ||
import type NodeCache from 'node-cache'; | ||
import { AuthenticationState } from './Auth'; | ||
import { AuthenticationState, AuthenticationCreds } from './Auth'; | ||
import { Chat, PresenceData } from './Chat'; | ||
@@ -61,3 +61,4 @@ import { Contact } from './Contact'; | ||
badSession = 500, | ||
restartRequired = 410 | ||
restartRequired = 410, | ||
notJoinedBeta = 403 | ||
} | ||
@@ -97,4 +98,4 @@ export declare type WAInitResponse = { | ||
'connection.update': Partial<ConnectionState>; | ||
/** auth state updated -- some pre keys, or identity keys etc. */ | ||
'auth-state.update': AuthenticationState; | ||
/** credentials updated -- some metadata, keys or something */ | ||
'creds.update': Partial<AuthenticationCreds>; | ||
/** set chats (history sync), messages are reverse chronologically sorted */ | ||
@@ -101,0 +102,0 @@ 'chats.set': { |
@@ -28,2 +28,3 @@ "use strict"; | ||
DisconnectReason[DisconnectReason["restartRequired"] = 410] = "restartRequired"; | ||
DisconnectReason[DisconnectReason["notJoinedBeta"] = 403] = "notJoinedBeta"; | ||
})(DisconnectReason = exports.DisconnectReason || (exports.DisconnectReason = {})); |
/// <reference types="node" /> | ||
import { AuthenticationState, WAPatchCreate, ChatMutation, WAPatchName, LTHashState, ChatModification } from "../Types"; | ||
import { AuthenticationState, WAPatchCreate, ChatMutation, WAPatchName, LTHashState, ChatModification, SignalKeyStore } from "../Types"; | ||
import { proto } from '../../WAProto'; | ||
import { BinaryNode } from '../WABinary'; | ||
export declare const mutationKeys: (keydata: Uint8Array) => { | ||
indexKey: Buffer; | ||
valueEncryptionKey: Buffer; | ||
valueMacKey: Buffer; | ||
snapshotMacKey: Buffer; | ||
patchMacKey: Buffer; | ||
}; | ||
export declare const generateSnapshotMac: (lthash: Uint8Array, version: number, name: WAPatchName, key: Buffer) => Buffer; | ||
export declare const newLTHashState: () => LTHashState; | ||
export declare const encodeSyncdPatch: ({ type, index, syncAction, apiVersion }: WAPatchCreate, { creds: { myAppStateKeyId }, keys }: AuthenticationState) => Promise<{ | ||
@@ -18,17 +11,53 @@ patch: proto.ISyncdPatch; | ||
hash: Buffer; | ||
mutations: ChatMutation[]; | ||
indexValueMap: { | ||
[indexMacBase64: string]: { | ||
valueMac: Uint8Array | Buffer; | ||
}; | ||
}; | ||
}; | ||
}>; | ||
export declare const decodeSyncdPatch: (msg: proto.ISyncdPatch, name: WAPatchName, { keys }: AuthenticationState, validateMacs?: boolean) => Promise<{ | ||
export declare const decodeSyncdMutations: (msgMutations: (proto.ISyncdMutation | proto.ISyncdRecord)[], initialState: LTHashState, getAppStateSyncKey: SignalKeyStore['getAppStateSyncKey'], validateMacs: boolean) => Promise<{ | ||
hash: Buffer; | ||
indexValueMap: { | ||
[indexMacBase64: string]: { | ||
valueMac: Uint8Array | Buffer; | ||
}; | ||
}; | ||
mutations: ChatMutation[]; | ||
}>; | ||
export declare const extractSyncdPatches: (result: BinaryNode) => { | ||
critical_block: proto.ISyncdPatch[]; | ||
critical_unblock_low: proto.ISyncdPatch[]; | ||
regular_low: proto.ISyncdPatch[]; | ||
regular_high: proto.ISyncdPatch[]; | ||
regular: proto.ISyncdPatch[]; | ||
}; | ||
export declare const decodeSyncdPatch: (msg: proto.ISyncdPatch, name: WAPatchName, initialState: LTHashState, getAppStateSyncKey: SignalKeyStore['getAppStateSyncKey'], validateMacs: boolean) => Promise<{ | ||
hash: Buffer; | ||
indexValueMap: { | ||
[indexMacBase64: string]: { | ||
valueMac: Uint8Array | Buffer; | ||
}; | ||
}; | ||
mutations: ChatMutation[]; | ||
}>; | ||
export declare const extractSyncdPatches: (result: BinaryNode) => Promise<{ | ||
critical_block: { | ||
patches: proto.ISyncdPatch[]; | ||
snapshot?: proto.ISyncdSnapshot; | ||
}; | ||
critical_unblock_low: { | ||
patches: proto.ISyncdPatch[]; | ||
snapshot?: proto.ISyncdSnapshot; | ||
}; | ||
regular_low: { | ||
patches: proto.ISyncdPatch[]; | ||
snapshot?: proto.ISyncdSnapshot; | ||
}; | ||
regular_high: { | ||
patches: proto.ISyncdPatch[]; | ||
snapshot?: proto.ISyncdSnapshot; | ||
}; | ||
regular: { | ||
patches: proto.ISyncdPatch[]; | ||
snapshot?: proto.ISyncdSnapshot; | ||
}; | ||
}>; | ||
export declare const downloadExternalBlob: (blob: proto.IExternalBlobReference) => Promise<Buffer>; | ||
export declare const downloadExternalPatch: (blob: proto.IExternalBlobReference) => Promise<proto.SyncdMutations>; | ||
export declare const decodePatches: (name: WAPatchName, syncds: proto.ISyncdPatch[], initial: LTHashState, auth: AuthenticationState, validateMacs?: boolean) => Promise<{ | ||
export declare const decodeSyncdSnapshot: (name: WAPatchName, snapshot: proto.ISyncdSnapshot, getAppStateSyncKey: SignalKeyStore['getAppStateSyncKey'], validateMacs?: boolean) => Promise<LTHashState>; | ||
export declare const decodePatches: (name: WAPatchName, syncds: proto.ISyncdPatch[], initial: LTHashState, getAppStateSyncKey: SignalKeyStore['getAppStateSyncKey'], validateMacs?: boolean) => Promise<{ | ||
newMutations: ChatMutation[]; | ||
@@ -35,0 +64,0 @@ state: LTHashState; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.chatModificationToAppPatch = exports.decodePatches = exports.downloadExternalPatch = exports.extractSyncdPatches = exports.decodeSyncdPatch = exports.encodeSyncdPatch = exports.generateSnapshotMac = exports.mutationKeys = void 0; | ||
exports.chatModificationToAppPatch = exports.decodePatches = exports.decodeSyncdSnapshot = exports.downloadExternalPatch = exports.downloadExternalBlob = exports.extractSyncdPatches = exports.decodeSyncdPatch = exports.decodeSyncdMutations = exports.encodeSyncdPatch = exports.newLTHashState = void 0; | ||
const boom_1 = require("@hapi/boom"); | ||
@@ -21,3 +21,2 @@ const crypto_1 = require("./crypto"); | ||
}; | ||
exports.mutationKeys = mutationKeys; | ||
const generateMac = (operation, data, keyId, key) => { | ||
@@ -49,23 +48,35 @@ const getKeyData = () => { | ||
}; | ||
const computeLtHash = (initial, macs, getPrevSetValueMac) => { | ||
const makeLtHashGenerator = ({ indexValueMap, hash }) => { | ||
indexValueMap = { ...indexValueMap }; | ||
const addBuffs = []; | ||
const subBuffs = []; | ||
for (let i = 0; i < macs.length; i++) { | ||
const { indexMac, valueMac, operation } = macs[i]; | ||
const subBuff = getPrevSetValueMac(indexMac, i); | ||
if (operation === WAProto_1.proto.SyncdMutation.SyncdMutationSyncdOperation.REMOVE) { | ||
if (!subBuff) { | ||
throw new boom_1.Boom('tried remove, but no buffer', { statusCode: 500 }); | ||
return { | ||
mix: ({ indexMac, valueMac, operation }) => { | ||
const indexMacBase64 = Buffer.from(indexMac).toString('base64'); | ||
const prevOp = indexValueMap[indexMacBase64]; | ||
if (operation === WAProto_1.proto.SyncdMutation.SyncdMutationSyncdOperation.REMOVE) { | ||
if (!prevOp) { | ||
throw new boom_1.Boom('tried remove, but no previous op', { data: { indexMac, valueMac } }); | ||
} | ||
// remove from index value mac, since this mutation is erased | ||
delete indexValueMap[indexMacBase64]; | ||
} | ||
else { | ||
addBuffs.push(new Uint8Array(valueMac).buffer); | ||
// add this index into the history map | ||
indexValueMap[indexMacBase64] = { valueMac }; | ||
} | ||
if (prevOp) { | ||
subBuffs.push(new Uint8Array(prevOp.valueMac).buffer); | ||
} | ||
}, | ||
finish: () => { | ||
const result = lt_hash_1.LT_HASH_ANTI_TAMPERING.subtractThenAdd(new Uint8Array(hash).buffer, addBuffs, subBuffs); | ||
const buffer = Buffer.from(result); | ||
return { | ||
hash: buffer, | ||
indexValueMap | ||
}; | ||
} | ||
else { | ||
addBuffs.push(new Uint8Array(valueMac).buffer); | ||
} | ||
if (subBuff) { | ||
subBuffs.push(new Uint8Array(subBuff).buffer); | ||
} | ||
} | ||
const result = lt_hash_1.LT_HASH_ANTI_TAMPERING.subtractThenAdd(new Uint8Array(initial).buffer, addBuffs, subBuffs); | ||
const buff = Buffer.from(result); | ||
return buff; | ||
}; | ||
}; | ||
@@ -80,3 +91,2 @@ const generateSnapshotMac = (lthash, version, name, key) => { | ||
}; | ||
exports.generateSnapshotMac = generateSnapshotMac; | ||
const generatePatchMac = (snapshotMac, valueMacs, version, type, key) => { | ||
@@ -91,2 +101,4 @@ const total = Buffer.concat([ | ||
}; | ||
const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} }); | ||
exports.newLTHashState = newLTHashState; | ||
const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion }, { creds: { myAppStateKeyId }, keys }) => { | ||
@@ -107,9 +119,12 @@ const key = !!myAppStateKeyId ? await keys.getAppStateSyncKey(myAppStateKeyId) : undefined; | ||
}).finish(); | ||
const keyValue = exports.mutationKeys(key.keyData); | ||
const keyValue = mutationKeys(key.keyData); | ||
const encValue = crypto_1.aesEncrypt(encoded, keyValue.valueEncryptionKey); | ||
const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey); | ||
const indexMac = crypto_1.hmacSign(indexBuffer, keyValue.indexKey); | ||
state.hash = computeLtHash(state.hash, [{ indexMac, valueMac, operation }], (index) => { var _a; return (_a = [...state.mutations].reverse().find(m => Buffer.compare(m.indexMac, index) === 0)) === null || _a === void 0 ? void 0 : _a.valueMac; }); | ||
// update LT hash | ||
const generator = makeLtHashGenerator(state); | ||
generator.mix({ indexMac, valueMac, operation }); | ||
Object.assign(state, generator.finish()); | ||
state.version += 1; | ||
const snapshotMac = exports.generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey); | ||
const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey); | ||
const patch = { | ||
@@ -134,16 +149,8 @@ patchMac: generatePatchMac(snapshotMac, [valueMac], state.version, type, keyValue.patchMacKey), | ||
}; | ||
state.mutations = [ | ||
...state.mutations, | ||
{ | ||
action: syncAction, | ||
index, | ||
valueMac, | ||
indexMac, | ||
operation | ||
} | ||
]; | ||
const base64Index = indexMac.toString('base64'); | ||
state.indexValueMap[base64Index] = { valueMac }; | ||
return { patch, state }; | ||
}; | ||
exports.encodeSyncdPatch = encodeSyncdPatch; | ||
const decodeSyncdPatch = async (msg, name, { keys }, validateMacs = true) => { | ||
const decodeSyncdMutations = async (msgMutations, initialState, getAppStateSyncKey, validateMacs) => { | ||
const keyCache = {}; | ||
@@ -154,7 +161,7 @@ const getKey = async (keyId) => { | ||
if (!key) { | ||
const keyEnc = await keys.getAppStateSyncKey(base64Key); | ||
const keyEnc = await getAppStateSyncKey(base64Key); | ||
if (!keyEnc) { | ||
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 500, data: msg }); | ||
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 500, data: { msgMutations } }); | ||
} | ||
const result = exports.mutationKeys(keyEnc.keyData); | ||
const result = mutationKeys(keyEnc.keyData); | ||
keyCache[base64Key] = result; | ||
@@ -165,15 +172,12 @@ key = result; | ||
}; | ||
const ltGenerator = makeLtHashGenerator(initialState); | ||
const mutations = []; | ||
if (validateMacs) { | ||
const mainKey = await getKey(msg.keyId.id); | ||
const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32)); | ||
const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, generics_1.toNumber(msg.version.version), name, mainKey.patchMacKey); | ||
if (Buffer.compare(patchMac, msg.patchMac) !== 0) { | ||
throw new boom_1.Boom('Invalid patch mac'); | ||
} | ||
} | ||
// indexKey used to HMAC sign record.index.blob | ||
// valueEncryptionKey used to AES-256-CBC encrypt record.value.blob[0:-32] | ||
// the remaining record.value.blob[0:-32] is the mac, it the HMAC sign of key.keyId + decoded proto data + length of bytes in keyId | ||
for (const { operation, record } of msg.mutations) { | ||
for (const msgMutation of msgMutations) { | ||
// if it's a syncdmutation, get the operation property | ||
// otherwise, if it's only a record -- it'll be a SET mutation | ||
const operation = 'operation' in msgMutation ? msgMutation.operation : WAProto_1.proto.SyncdMutation.SyncdMutationSyncdOperation.SET; | ||
const record = ('record' in msgMutation && !!msgMutation.record) ? msgMutation.record : msgMutation; | ||
const key = await getKey(record.keyId.id); | ||
@@ -198,3 +202,3 @@ const content = Buffer.from(record.value.blob); | ||
const indexStr = Buffer.from(syncAction.index).toString(); | ||
mutations.push({ | ||
const mutation = { | ||
action: syncAction.value, | ||
@@ -205,18 +209,48 @@ index: JSON.parse(indexStr), | ||
operation: operation | ||
}); | ||
}; | ||
mutations.push(mutation); | ||
ltGenerator.mix(mutation); | ||
} | ||
return { mutations }; | ||
return { mutations, ...ltGenerator.finish() }; | ||
}; | ||
exports.decodeSyncdMutations = decodeSyncdMutations; | ||
const decodeSyncdPatch = async (msg, name, initialState, getAppStateSyncKey, validateMacs) => { | ||
if (validateMacs) { | ||
const base64Key = Buffer.from(msg.keyId.id).toString('base64'); | ||
const mainKeyObj = await getAppStateSyncKey(base64Key); | ||
const mainKey = mutationKeys(mainKeyObj.keyData); | ||
const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32)); | ||
const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, generics_1.toNumber(msg.version.version), name, mainKey.patchMacKey); | ||
if (Buffer.compare(patchMac, msg.patchMac) !== 0) { | ||
throw new boom_1.Boom('Invalid patch mac'); | ||
} | ||
} | ||
const result = await exports.decodeSyncdMutations(msg.mutations, initialState, getAppStateSyncKey, validateMacs); | ||
return result; | ||
}; | ||
exports.decodeSyncdPatch = decodeSyncdPatch; | ||
const extractSyncdPatches = (result) => { | ||
const extractSyncdPatches = async (result) => { | ||
const syncNode = WABinary_1.getBinaryNodeChild(result, 'sync'); | ||
const collectionNodes = WABinary_1.getBinaryNodeChildren(syncNode, 'collection'); | ||
const final = {}; | ||
for (const collectionNode of collectionNodes) { | ||
await Promise.all(collectionNodes.map(async (collectionNode) => { | ||
const patchesNode = WABinary_1.getBinaryNodeChild(collectionNode, 'patches'); | ||
const patches = WABinary_1.getBinaryNodeChildren(patchesNode || collectionNode, 'patch'); | ||
const snapshotNode = WABinary_1.getBinaryNodeChild(collectionNode, 'snapshot'); | ||
const syncds = []; | ||
const name = collectionNode.attrs.name; | ||
let snapshot = undefined; | ||
if (snapshotNode && !!snapshotNode.content) { | ||
if (!Buffer.isBuffer(snapshotNode)) { | ||
snapshotNode.content = Buffer.from(Object.values(snapshotNode.content)); | ||
} | ||
const blobRef = WAProto_1.proto.ExternalBlobReference.decode(snapshotNode.content); | ||
const data = await exports.downloadExternalBlob(blobRef); | ||
snapshot = WAProto_1.proto.SyncdSnapshot.decode(data); | ||
} | ||
for (let { content } of patches) { | ||
if (content) { | ||
if (!Buffer.isBuffer(content)) { | ||
content = Buffer.from(Object.values(content)); | ||
} | ||
const syncd = WAProto_1.proto.SyncdPatch.decode(content); | ||
@@ -229,8 +263,8 @@ if (!syncd.version) { | ||
} | ||
final[name] = syncds; | ||
} | ||
final[name] = { patches: syncds, snapshot }; | ||
})); | ||
return final; | ||
}; | ||
exports.extractSyncdPatches = extractSyncdPatches; | ||
const downloadExternalPatch = async (blob) => { | ||
const downloadExternalBlob = async (blob) => { | ||
const stream = await messages_media_1.downloadContentFromMessage(blob, 'md-app-state'); | ||
@@ -241,2 +275,7 @@ let buffer = Buffer.from([]); | ||
} | ||
return buffer; | ||
}; | ||
exports.downloadExternalBlob = downloadExternalBlob; | ||
const downloadExternalPatch = async (blob) => { | ||
const buffer = await exports.downloadExternalBlob(blob); | ||
const syncData = WAProto_1.proto.SyncdMutations.decode(buffer); | ||
@@ -246,61 +285,60 @@ return syncData; | ||
exports.downloadExternalPatch = downloadExternalPatch; | ||
const decodePatches = async (name, syncds, initial, auth, validateMacs = true) => { | ||
const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, validateMacs = true) => { | ||
const newState = exports.newLTHashState(); | ||
newState.version = generics_1.toNumber(snapshot.version.version); | ||
const records = snapshot.records; | ||
const ltGenerator = makeLtHashGenerator(newState); | ||
for (const { index, value } of records) { | ||
const valueMac = value.blob.slice(-32); | ||
ltGenerator.mix({ indexMac: index.blob, valueMac, operation: 0 }); | ||
} | ||
Object.assign(newState, ltGenerator.finish()); | ||
if (validateMacs) { | ||
const base64Key = Buffer.from(snapshot.keyId.id).toString('base64'); | ||
const keyEnc = await getAppStateSyncKey(base64Key); | ||
if (!keyEnc) { | ||
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 500 }); | ||
} | ||
const result = mutationKeys(keyEnc.keyData); | ||
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey); | ||
if (Buffer.compare(snapshot.mac, computedSnapshotMac) !== 0) { | ||
throw new boom_1.Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`, { statusCode: 500 }); | ||
} | ||
} | ||
return newState; | ||
}; | ||
exports.decodeSyncdSnapshot = decodeSyncdSnapshot; | ||
const decodePatches = async (name, syncds, initial, getAppStateSyncKey, validateMacs = true) => { | ||
const successfulMutations = []; | ||
let current = initial.hash; | ||
let currentVersion = initial.version; | ||
const newState = { | ||
...initial, | ||
indexValueMap: { ...initial.indexValueMap } | ||
}; | ||
for (const syncd of syncds) { | ||
const { mutations, version, keyId, snapshotMac, externalMutations } = syncd; | ||
if (externalMutations) { | ||
const ref = await exports.downloadExternalPatch(externalMutations); | ||
mutations.push(...ref.mutations); | ||
const { version, keyId, snapshotMac } = syncd; | ||
if (syncd.externalMutations) { | ||
const ref = await exports.downloadExternalPatch(syncd.externalMutations); | ||
syncd.mutations.push(...ref.mutations); | ||
} | ||
const macs = mutations.map(m => ({ | ||
operation: m.operation, | ||
indexMac: m.record.index.blob, | ||
valueMac: m.record.value.blob.slice(-32) | ||
})); | ||
currentVersion = generics_1.toNumber(version.version); | ||
current = computeLtHash(current, macs, (index, maxIndex) => { | ||
let value; | ||
for (const item of initial.mutations) { | ||
if (Buffer.compare(item.indexMac, index) === 0) { | ||
value = item.valueMac; | ||
} | ||
} | ||
for (const { version, mutations } of syncds) { | ||
const versionNum = generics_1.toNumber(version.version); | ||
const mutationIdx = mutations.findIndex(m => { | ||
return Buffer.compare(m.record.index.blob, index) === 0; | ||
}); | ||
if (mutationIdx >= 0 && (versionNum < currentVersion || mutationIdx < maxIndex)) { | ||
value = mutations[mutationIdx].record.value.blob.slice(-32); | ||
} | ||
if (versionNum >= currentVersion) { | ||
break; | ||
} | ||
} | ||
return value; | ||
}); | ||
newState.version = generics_1.toNumber(version.version); | ||
const decodeResult = await exports.decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, validateMacs); | ||
newState.hash = decodeResult.hash; | ||
newState.indexValueMap = decodeResult.indexValueMap; | ||
successfulMutations.push(...decodeResult.mutations); | ||
if (validateMacs) { | ||
const base64Key = Buffer.from(keyId.id).toString('base64'); | ||
const keyEnc = await auth.keys.getAppStateSyncKey(base64Key); | ||
const keyEnc = await getAppStateSyncKey(base64Key); | ||
if (!keyEnc) { | ||
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 500 }); | ||
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`); | ||
} | ||
const result = exports.mutationKeys(keyEnc.keyData); | ||
const computedSnapshotMac = exports.generateSnapshotMac(current, currentVersion, name, result.snapshotMacKey); | ||
const result = mutationKeys(keyEnc.keyData); | ||
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey); | ||
if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) { | ||
throw new boom_1.Boom(`failed to verify LTHash at ${currentVersion} of ${name}`, { statusCode: 500 }); | ||
throw new boom_1.Boom(`failed to verify LTHash at ${newState.version} of ${name}`); | ||
} | ||
} | ||
const decodeResult = await exports.decodeSyncdPatch(syncd, name, auth, validateMacs); | ||
successfulMutations.push(...decodeResult.mutations); | ||
} | ||
return { | ||
newMutations: successfulMutations, | ||
state: { | ||
hash: current, | ||
version: currentVersion, | ||
mutations: [...initial.mutations, ...successfulMutations] | ||
} | ||
state: newState | ||
}; | ||
@@ -307,0 +345,0 @@ }; |
@@ -12,1 +12,2 @@ export * from './decode-wa-message'; | ||
export * from './lt-hash'; | ||
export * from './auth-utils'; |
@@ -24,1 +24,2 @@ "use strict"; | ||
__exportStar(require("./lt-hash"), exports); | ||
__exportStar(require("./auth-utils"), exports); |
@@ -90,3 +90,3 @@ "use strict"; | ||
delete uploadData.media; | ||
const content = { | ||
const obj = Types_1.WAProto.Message.fromObject({ | ||
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({ | ||
@@ -100,6 +100,5 @@ url: mediaUrl, | ||
}) | ||
}; | ||
const obj = Types_1.WAProto.Message.fromObject(content); | ||
}); | ||
if (cacheableKey) { | ||
options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj)); | ||
options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish()); | ||
} | ||
@@ -106,0 +105,0 @@ return obj; |
/// <reference types="node" /> | ||
import { SignalIdentity, SignalKeyStore, SignedKeyPair, KeyPair, AuthenticationState } from "../Types/Auth"; | ||
import { SignalIdentity, SignalKeyStore, SignedKeyPair, KeyPair, SignalAuthState, AuthenticationCreds } from "../Types/Auth"; | ||
import { BinaryNode, JidWithDevice } from "../WABinary"; | ||
@@ -12,3 +12,3 @@ import { proto } from "../../WAProto"; | ||
}>; | ||
export declare const generateOrGetPreKeys: ({ creds }: AuthenticationState, range: number) => { | ||
export declare const generateOrGetPreKeys: (creds: AuthenticationCreds, range: number) => { | ||
newPreKeys: { | ||
@@ -22,3 +22,3 @@ [id: number]: KeyPair; | ||
export declare const xmppPreKey: (pair: KeyPair, id: number) => BinaryNode; | ||
export declare const signalStorage: ({ creds, keys }: AuthenticationState) => { | ||
export declare const signalStorage: ({ creds, keys }: SignalAuthState) => { | ||
loadSession: (id: any) => Promise<any>; | ||
@@ -44,14 +44,14 @@ storeSession: (id: any, session: any) => Promise<void>; | ||
}; | ||
export declare const decryptGroupSignalProto: (group: string, user: string, msg: Buffer | Uint8Array, auth: AuthenticationState) => any; | ||
export declare const processSenderKeyMessage: (authorJid: string, item: proto.ISenderKeyDistributionMessage, auth: AuthenticationState) => Promise<void>; | ||
export declare const decryptSignalProto: (user: string, type: 'pkmsg' | 'msg', msg: Buffer | Uint8Array, auth: AuthenticationState) => Promise<Buffer>; | ||
export declare const encryptSignalProto: (user: string, buffer: Buffer, auth: AuthenticationState) => Promise<{ | ||
export declare const decryptGroupSignalProto: (group: string, user: string, msg: Buffer | Uint8Array, auth: SignalAuthState) => any; | ||
export declare const processSenderKeyMessage: (authorJid: string, item: proto.ISenderKeyDistributionMessage, auth: SignalAuthState) => Promise<void>; | ||
export declare const decryptSignalProto: (user: string, type: 'pkmsg' | 'msg', msg: Buffer | Uint8Array, auth: SignalAuthState) => Promise<Buffer>; | ||
export declare const encryptSignalProto: (user: string, buffer: Buffer, auth: SignalAuthState) => Promise<{ | ||
type: string; | ||
ciphertext: Buffer; | ||
}>; | ||
export declare const encryptSenderKeyMsgSignalProto: (group: string, data: Uint8Array | Buffer, auth: AuthenticationState) => Promise<{ | ||
export declare const encryptSenderKeyMsgSignalProto: (group: string, data: Uint8Array | Buffer, meId: string, auth: SignalAuthState) => Promise<{ | ||
ciphertext: Uint8Array; | ||
senderKeyDistributionMessageKey: Buffer; | ||
}>; | ||
export declare const parseAndInjectE2ESession: (node: BinaryNode, auth: AuthenticationState) => Promise<void>; | ||
export declare const parseAndInjectE2ESession: (node: BinaryNode, auth: SignalAuthState) => Promise<void>; | ||
export declare const extractDeviceJids: (result: BinaryNode, myDeviceId: number, excludeZeroDevices: boolean) => JidWithDevice[]; |
@@ -61,3 +61,3 @@ "use strict"; | ||
exports.getPreKeys = getPreKeys; | ||
const generateOrGetPreKeys = ({ creds }, range) => { | ||
const generateOrGetPreKeys = (creds, range) => { | ||
const avaliable = creds.nextPreKeyId - creds.firstUnuploadedPreKeyId; | ||
@@ -189,5 +189,5 @@ const remaining = range - avaliable; | ||
exports.encryptSignalProto = encryptSignalProto; | ||
const encryptSenderKeyMsgSignalProto = async (group, data, auth) => { | ||
const encryptSenderKeyMsgSignalProto = async (group, data, meId, auth) => { | ||
const storage = exports.signalStorage(auth); | ||
const senderName = exports.jidToSignalSenderKeyName(group, auth.creds.me.id); | ||
const senderName = exports.jidToSignalSenderKeyName(group, meId); | ||
const builder = new WASignalGroup_1.GroupSessionBuilder(storage); | ||
@@ -194,0 +194,0 @@ const senderKey = await auth.keys.getSenderKey(senderName); |
@@ -1,24 +0,5 @@ | ||
import { proto } from '../../WAProto'; | ||
import type { AuthenticationState, SocketConfig, SignalKeyStore, AuthenticationCreds, KeyPair, LTHashState } from "../Types"; | ||
import type { SocketConfig, AuthenticationCreds, SignalCreds } from "../Types"; | ||
import { BinaryNode } from '../WABinary'; | ||
export declare const generateLoginNode: (userJid: string, config: Pick<SocketConfig, 'version' | 'browser'>) => Uint8Array; | ||
export declare const generateRegistrationNode: ({ registrationId, signedPreKey, signedIdentityKey }: Pick<AuthenticationCreds, 'registrationId' | 'signedPreKey' | 'signedIdentityKey'>, config: Pick<SocketConfig, 'version' | 'browser'>) => Uint8Array; | ||
export declare const initInMemoryKeyStore: ({ preKeys, sessions, senderKeys, appStateSyncKeys, appStateVersions }?: { | ||
preKeys?: { | ||
[k: number]: KeyPair; | ||
}; | ||
sessions?: { | ||
[k: string]: any; | ||
}; | ||
senderKeys?: { | ||
[k: string]: any; | ||
}; | ||
appStateSyncKeys?: { | ||
[k: string]: proto.IAppStateSyncKeyData; | ||
}; | ||
appStateVersions?: { | ||
[k: string]: LTHashState; | ||
}; | ||
}) => SignalKeyStore; | ||
export declare const initAuthState: () => AuthenticationState; | ||
export declare const generateRegistrationNode: ({ registrationId, signedPreKey, signedIdentityKey }: SignalCreds, config: Pick<SocketConfig, 'version' | 'browser'>) => Uint8Array; | ||
export declare const configureSuccessfulPairing: (stanza: BinaryNode, { advSecretKey, signedIdentityKey, signalIdentities }: Pick<AuthenticationCreds, 'advSecretKey' | 'signedIdentityKey' | 'signalIdentities'>) => { | ||
@@ -25,0 +6,0 @@ creds: Partial<AuthenticationCreds>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.configureSuccessfulPairing = exports.initAuthState = exports.initInMemoryKeyStore = exports.generateRegistrationNode = exports.generateLoginNode = void 0; | ||
exports.configureSuccessfulPairing = exports.generateRegistrationNode = exports.generateLoginNode = void 0; | ||
const boom_1 = require("@hapi/boom"); | ||
const crypto_1 = require("crypto"); | ||
const WAProto_1 = require("../../WAProto"); | ||
const crypto_2 = require("./crypto"); | ||
const crypto_1 = require("./crypto"); | ||
const generics_1 = require("./generics"); | ||
@@ -78,81 +77,2 @@ const WABinary_1 = require("../WABinary"); | ||
exports.generateRegistrationNode = generateRegistrationNode; | ||
const initInMemoryKeyStore = ({ preKeys, sessions, senderKeys, appStateSyncKeys, appStateVersions } = {}) => { | ||
preKeys = preKeys || {}; | ||
sessions = sessions || {}; | ||
senderKeys = senderKeys || {}; | ||
appStateSyncKeys = appStateSyncKeys || {}; | ||
appStateVersions = appStateVersions || {}; | ||
return { | ||
preKeys, | ||
sessions, | ||
senderKeys, | ||
appStateSyncKeys, | ||
appStateVersions, | ||
getPreKey: keyId => preKeys[keyId], | ||
setPreKey: (keyId, pair) => { | ||
if (pair) | ||
preKeys[keyId] = pair; | ||
else | ||
delete preKeys[keyId]; | ||
}, | ||
getSession: id => sessions[id], | ||
setSession: (id, item) => { | ||
if (item) | ||
sessions[id] = item; | ||
else | ||
delete sessions[id]; | ||
}, | ||
getSenderKey: id => { | ||
return senderKeys[id]; | ||
}, | ||
setSenderKey: (id, item) => { | ||
if (item) | ||
senderKeys[id] = item; | ||
else | ||
delete senderKeys[id]; | ||
}, | ||
getAppStateSyncKey: id => { | ||
const obj = appStateSyncKeys[id]; | ||
if (obj) { | ||
return WAProto_1.proto.AppStateSyncKeyData.fromObject(obj); | ||
} | ||
}, | ||
setAppStateSyncKey: (id, item) => { | ||
if (item) | ||
appStateSyncKeys[id] = item; | ||
else | ||
delete appStateSyncKeys[id]; | ||
}, | ||
getAppStateSyncVersion: id => { | ||
const obj = appStateVersions[id]; | ||
if (obj) { | ||
return obj; | ||
} | ||
}, | ||
setAppStateSyncVersion: (id, item) => { | ||
if (item) | ||
appStateVersions[id] = item; | ||
else | ||
delete appStateVersions[id]; | ||
} | ||
}; | ||
}; | ||
exports.initInMemoryKeyStore = initInMemoryKeyStore; | ||
const initAuthState = () => { | ||
const identityKey = crypto_2.Curve.generateKeyPair(); | ||
return { | ||
creds: { | ||
noiseKey: crypto_2.Curve.generateKeyPair(), | ||
signedIdentityKey: identityKey, | ||
signedPreKey: crypto_2.signedKeyPair(identityKey, 1), | ||
registrationId: generics_1.generateRegistrationId(), | ||
advSecretKey: crypto_1.randomBytes(32).toString('base64'), | ||
nextPreKeyId: 1, | ||
firstUnuploadedPreKeyId: 1, | ||
serverHasPreKeys: false | ||
}, | ||
keys: exports.initInMemoryKeyStore() | ||
}; | ||
}; | ||
exports.initAuthState = initAuthState; | ||
const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentityKey, signalIdentities }) => { | ||
@@ -168,3 +88,3 @@ var _a, _b, _c, _d, _e; | ||
const { details, hmac } = WAProto_1.proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentity); | ||
const advSign = crypto_2.hmacSign(details, Buffer.from(advSecretKey, 'base64')); | ||
const advSign = crypto_1.hmacSign(details, Buffer.from(advSecretKey, 'base64')); | ||
if (Buffer.compare(hmac, advSign) !== 0) { | ||
@@ -176,7 +96,7 @@ throw new boom_1.Boom('Invalid pairing'); | ||
const accountMsg = WABinary_1.Binary.build(new Uint8Array([6, 0]), account.details, signedIdentityKey.public).readByteArray(); | ||
if (!crypto_2.Curve.verify(accountSignatureKey, accountMsg, accountSignature)) { | ||
if (!crypto_1.Curve.verify(accountSignatureKey, accountMsg, accountSignature)) { | ||
throw new boom_1.Boom('Failed to verify account signature'); | ||
} | ||
const deviceMsg = WABinary_1.Binary.build(new Uint8Array([6, 1]), account.details, signedIdentityKey.public, account.accountSignatureKey).readByteArray(); | ||
account.deviceSignature = crypto_2.Curve.sign(signedIdentityKey.private, deviceMsg); | ||
account.deviceSignature = crypto_1.Curve.sign(signedIdentityKey.private, deviceMsg); | ||
const identity = signal_1.createSignalIdentity(jid, accountSignatureKey); | ||
@@ -183,0 +103,0 @@ const keyIndex = WAProto_1.proto.ADVDeviceIdentity.decode(account.details).keyIndex; |
{ | ||
"name": "md-wa", | ||
"version": "4.1.0", | ||
"version": "5.0.0", | ||
"description": "WhatsApp Multi-Device API", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/adiwajshing/Baileys", |
@@ -120,46 +120,19 @@ # Baileys MD - Typescript/Javascript WhatsApp Web API | ||
So, you can save the credentials to log back in via: | ||
So, you can load the credentials to log back in: | ||
``` ts | ||
import makeWASocket, { BufferJSON } from '@adiwajshing/baileys-md' | ||
import makeWASocket, { BufferJSON, useSingleFileAuthState } from '@adiwajshing/baileys-md' | ||
import * as fs from 'fs' | ||
// will initialize a default in-memory auth session | ||
const conn = makeSocket() | ||
// utility function to help save the auth state in a single file | ||
// it's utility ends at demos -- as re-writing a large file over and over again is very inefficient | ||
const { state, saveState } = useSingleFileAuthState('./auth_info_multi.json') | ||
// will use the given state to connect | ||
// so if valid credentials are available -- it'll connect without QR | ||
const conn = makeSocket({ auth: state }) | ||
// this will be called as soon as the credentials are updated | ||
conn.ev.on ('auth-state.update', () => { | ||
// save credentials whenever updated | ||
console.log (`credentials updated!`) | ||
const authInfo = conn.authState // get all the auth info we need to restore this session | ||
// save this info to a file | ||
fs.writeFileSync( | ||
'./auth_info.json', | ||
JSON.stringify(authInfo, BufferJSON.replacer, 2) | ||
) | ||
}) | ||
conn.ev.on ('creds.update', saveState) | ||
``` | ||
Then, to restore a session: | ||
``` ts | ||
import makeWASocket, { BufferJSON, initInMemoryKeyStore } from '@adiwajshing/baileys-md' | ||
import * as fs from 'fs' | ||
**Note**: When a message is received/sent, due to signal sessions needing updating, the auth keys (`authState.keys`) will update. Whenever that happens, you must save the updated keys. Not doing so will prevent your messages from reaching the recipient & other unexpected consequences. The `useSingleFileAuthState` function automatically takes care of that, but for any other serious implementation -- you will need to be very careful with the key state management. | ||
const authJSON = JSON.parse( | ||
fs.readFileSync( | ||
'./auth_info.json', | ||
{ encoding: 'utf-8' } | ||
), | ||
BufferJSON.reviver | ||
) | ||
const auth = { | ||
creds: authJSON.creds, | ||
// stores pre-keys, session & other keys in a JSON object | ||
// we deserialize it here | ||
keys: initInMemoryKeyStore(authJSON.keys) | ||
} | ||
const conn = makeWASocket(auth) | ||
// yay will connect without scanning QR | ||
``` | ||
**Note**: Upon every successive connection, the auth state can update part of the stored credentials. It will also update when a message is received/sent due to signal sessions needing updating. Whenever that happens, the `auth-state.update` event is fired uploaded, and you must update your saved credentials upon receiving the event. Not doing so will prevent your messages from reaching the recipient & other unexpected consequences. | ||
## Listening to Connection Updates | ||
@@ -200,4 +173,4 @@ | ||
'connection.update': Partial<ConnectionState> | ||
/** auth state updated -- some pre keys, or identity keys etc. */ | ||
'auth-state.update': AuthenticationState | ||
/** auth credentials updated -- some pre key state, device ID etc. */ | ||
'creds.update': Partial<AuthenticationCreds> | ||
/** set chats (history sync), messages are reverse chronologically sorted */ | ||
@@ -605,3 +578,3 @@ 'chats.set': { chats: Chat[], messages: WAMessage[] } | ||
const metadata = await conn.groupMetadata("abcd-xyz@g.us") | ||
console.log(json.id + ", title: " + json.subject + ", description: " + json.desc) | ||
console.log(metadata.id + ", title: " + metadata.subject + ", description: " + metadata.desc) | ||
``` | ||
@@ -608,0 +581,0 @@ - To join the group using the invitation code (not supported yet) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3649021
31
85
73180
634
4