react-native-matrix-crypto
    Preparing search index...

    Function onCryptoSignal

    • Subscribes to the crypto signal channel. Returns an unsubscribe function.

      Three of the five variants have a producer, and all three belong to device verification. Subscribing installs the native observer, so a listener registered here starts receiving as soon as this call returns; the channel was silent for the whole of M1 and M2, and this is where that stops being true.

      • verification_requested -- another device has asked to verify itself against this one, and verificationId is what you pass to acceptVerification. This is the only way this library hands you that identifier: there is no call that lists inbound flows. The value itself is the transaction_id on the wire, and before this signal existed a product had to go and get it -- filter its own to_device_events for m.key.verification.request and read the field out of one, which is a protocol detail this library keeps to itself everywhere else.

      • trust_changed -- something this library will now say differently about user changed. Two things produce it and they are indistinguishable from the value alone, which is why the rule for this variant is to read rather than to count. A comparison finished and a device belonging to user moved: getDeviceStatuses for that user says which. A verification finished by a scanned code moves the same device and produces no trust_changed at all, so a product that waits on this variant after confirmScan or submitScannedCode waits forever. Such a flow announces verification_completed instead, which is the next bullet and says why the two are not one; reading getDeviceStatuses is what both of them tell you to do. Or, when user is your own user id, the account's private signing keys arrived on this device by gossip from another of your devices, after requestSelfVerification: getIdentityStatus says so, with privateKeysHeld === true, and that is the moment a new login becomes able to sign. A self-verification produces both, on consecutive syncs, so a product that reads both answers when told is correct under either.

      • verification_completed -- a flow that was verified by scanning a code has finished, and verificationId names it. It arrives on both screens: the one that showed a code and called confirmScan, and the one that read a code and called submitScannedCode. Without it a product had no way at all to learn that a code verification succeeded, because no call returns when the other side acknowledges, and getVerificationStage would have to be polled.

        It is not a trust_changed, and the difference is not cosmetic. In two of the three modes the protocol defines, nothing about a device changes at this moment: what those flows verify is an identity. And for another user, getDeviceStatuses still reads unverified when this arrives, because verifying them signs their master key and your store does not carry that signature until a later key query brings it back. So read the durable answers when you get this, exactly as for trust_changed, and expect another user's devices to turn verified a sync or two later rather than instantly.

        Only a flow verified by a code produces it. A short-string comparison announces its completion as trust_changed and nothing else, and a flow that was refused or timed out announces nothing at all: getVerificationStage is what says 'cancelled'.

        That asymmetry is a known limit of this release, and it is the one that will cost you code. The two variants carry disjoint halves of the same fact: trust_changed names a user and no flow, so you cannot tell which of two verifications with that user finished; verification_completed names a flow and no trust. So "show a success screen for this verification" is two paths, and the side that received an invitation cannot know in advance which it will get, because the peer decides that by scanning a code or by starting a string comparison. Hold your own map from verificationId to what you are showing, and treat either signal as "read the durable answer now".

        The fix is additive and is deferred rather than forgotten: every completed flow announcing verification_completed, with trust_changed left exactly as it is. Nothing already true would stop being true. It is not in this release because it reaches back into the short-string flows settled two milestones ago, and re-settling those belongs to a change that can carry them rather than to the corner of one that added codes.

      • unexpected_device and key_missing still have no producer. The conditions they name do occur, and reach you elsewhere: a missing key arrives as a rejected decryptEvent with kind missing_key, not as a key_missing signal here.

      When they arrive, and what has to have happened first

      Every producer runs inside receiveSyncChanges. Nothing is announced on a timer, and nothing is announced for an event you have not fed in. A product that stops syncing stops being told.

      An invitation from a device this library has never been told about builds no flow, and so is not announced. That is not a gap in the channel; it is the channel refusing to hand you an identifier no call here would answer to. See acceptVerification for the recovery, which is unchanged except that you no longer have to read anything out of the event you kept.

      What happens across an unsubscribe, which is less than you might fear

      The channel re-offers what is still live rather than replaying what it once sent. Nothing is queued for a subscriber that is not there; what happens instead is that nothing is consumed while nobody is listening, because the native producer does no work at all with no observer installed. So an invitation that arrives while you are unsubscribed is still requested when you come back, and the first receiveSyncChanges after you resubscribe announces it. Subscribe at start-up if you can, but useEffect(() => onCryptoSignal(h), []) does not lose those -- with the one exception named below, which is a real exception and not a hedge.

      Four things it genuinely does not do. A comparison's trust_changed is not re-offered -- ask getDeviceStatuses, which is the durable answer and always was. Its sibling behaves the other way and the two are worth keeping apart: the private-keys arrival is re-offered, because the latch that makes it fire once is only touched while somebody is listening, so an arrival that happened while you were away is announced on the first sync after you resubscribe. getIdentityStatus is still the durable answer to it, and reading that is still the rule. A hot reload leaves the previous module copy's observer installed until something subscribes again; an invitation arriving in that window is consumed by a listener set nothing can reach. An unsubscribe can land in the last instant of a sync, after the native side has read its observer and before the signal reaches this module, and an invitation caught there is not re-offered either -- narrower than it sounds, because resubscribing before the delivery arrives still receives it, and closing it entirely would mean your sync call holding a lock across a call into JavaScript. And one shape of invitation is not re-offered at all: a peer that opens the comparison directly, without asking first -- see acceptVerification for who does that and why it makes no difference to your code -- leaves nothing behind that can be enumerated on a later sync. The sync that carried it is its only witness, which is why "subscribe at start-up if you can" above is the stronger advice for it.

      A listener that throws does not affect the others, and does not affect the sync that produced the signal: delivery happens on a thread of the library's own, after the call that caused it has completed.

      It throws rather than hand you a channel that is not there

      Returning normally has exactly one meaning: the native observer is installed and this listener is on it. Installing is the only thing this call does that can fail, and it fails as a whole, so a failure is reported by throwing out of here and never by returning an unsubscribe function for something that will not deliver. The realistic cause is the native module not being reachable, which index.ts describes; the throw is whatever the generated binding raised, not a type of this library's own, because there is nothing this layer could add to it.

      Nothing is left behind by a throw. The listener is not registered, so a caller that catches one is not subscribed and is not silently holding the observer open behind an empty set, and the next call here attempts the install again rather than assuming it is beyond help.

      The ordinary integration subscribes inside an effect, where a throw reaches the nearest error boundary rather than the code that called this, and that is the intended outcome and not a wrinkle: it is the same treatment the rest of this surface gives an unusable native module, and it is louder than any value this function could return, all of which a caller is free to ignore. What a product must never be able to do is wait on this channel for a signal that was never going to arrive.

      Parameters

      Returns Unsubscribe