From d1bf26610a1aa98cd9f8c945b74863d34cfcfa5b Mon Sep 17 00:00:00 2001 From: Godefroy Ponsinet Date: Thu, 6 Dec 2018 18:09:36 +0100 Subject: [PATCH] fix(rn): conversation read Signed-off-by: Godefroy Ponsinet --- .../common/components/Screens/Chats/Add.js | 1 - .../common/components/Screens/Chats/Detail.js | 47 +- .../common/components/Screens/Chats/List.js | 16 +- .../Screens/Contacts/Detail/PublicKey.js | 1 - .../components/Screens/Contacts/List.js | 1 - .../Screens/Settings/Devtools/DeviceInfos.js | 1 - .../react-native/common/graphql/enums.gen.js | 4 + .../graphql/mutations/ConversationCreate.js | 1 - .../common/graphql/queries/Conversation.js | 2 +- .../graphql/subscriptions/ContactRequest.js | 1 - .../subscriptions/ConversationNewMessage.js | 1 - .../graphql/subscriptions/ConversationRead.js | 23 + .../common/graphql/subscriptions/EventSeen.js | 39 ++ .../graphql/subscriptions/EventStream.js | 3 +- .../graphql/subscriptions/NewMessage.js | 15 + .../common/graphql/subscriptions/index.js | 3 + .../react-native/common/relay/RelayContext.js | 1 - .../react-native/common/relay/environment.js | 2 - .../common/relay/genericUpdater.js | 7 +- client/react-native/common/schema.graphql | 6 + core/api/node/graphql/enums.gen.js | 4 + core/api/node/graphql/gqlgen.gen.yml | 26 + .../graphql/graph/generated/generated.gen.go | 77 +++ core/api/node/graphql/service.gen.graphql | 6 + core/api/p2p/kind.gen.go | 42 ++ core/api/p2p/kind.pb.go | 484 +++++++++++++++--- core/api/p2p/kind.proto | 6 + core/node/event.go | 12 +- core/node/event_handlers.go | 64 +++ core/node/nodeapi.go | 47 +- core/node/p2pclient.go | 13 + 31 files changed, 831 insertions(+), 125 deletions(-) create mode 100644 client/react-native/common/graphql/subscriptions/ConversationRead.js create mode 100644 client/react-native/common/graphql/subscriptions/EventSeen.js create mode 100644 client/react-native/common/graphql/subscriptions/NewMessage.js diff --git a/client/react-native/common/components/Screens/Chats/Add.js b/client/react-native/common/components/Screens/Chats/Add.js index 90f509f136..695efdfdbe 100644 --- a/client/react-native/common/components/Screens/Chats/Add.js +++ b/client/react-native/common/components/Screens/Chats/Add.js @@ -156,7 +156,6 @@ export default class ListScreen extends Component { { - console.log(props) const index = contactsID.lastIndexOf(props.data.id) index < 0 ? contactsID.push(props.data.id) diff --git a/client/react-native/common/components/Screens/Chats/Detail.js b/client/react-native/common/components/Screens/Chats/Detail.js index f591841eb6..787d5ec33e 100644 --- a/client/react-native/common/components/Screens/Chats/Detail.js +++ b/client/react-native/common/components/Screens/Chats/Detail.js @@ -13,31 +13,10 @@ import { parseEmbedded } from '../../../helpers/json' class Message extends React.PureComponent { static contextType = RelayContext - async componentDidMount () { - const conversation = this.props.navigation.getParam('conversation') - const contactId = this.props.data.senderId - const isMyself = - conversation.members.find(m => m.contactId === contactId).contact - .status === 42 - - if (isMyself || this.props.data.seenAt !== null) { - return - } - + messageSeen = async () => { await this.props.screenProps.context.mutations.eventSeen({ id: this.props.data.id, }) - await this.props.screenProps.context.mutations.conversationRead({ - id: conversation.id, - }) - } - - async componentDidUpdate (prevProps) { - if (prevProps.data.id === this.props.data.id) { - return - } - - await this.componentDidMount() } render () { @@ -49,6 +28,10 @@ class Message extends React.PureComponent { const { data } = this.props + // TODO: implement message seen + // if (new Date(this.props.data.seenAt).getTime() <= 0) { + // this.messageSeen() + // } return ( {new Date(data.createdAt).toTimeString()}{' '} {isMyself ? ( - + 0 ? 'check-circle' : 'circle' + } + /> ) : null}{' '} - {' '} + 0 ? 'eye' : 'eye-off'} + />{' '} {/* TODO: used for debugging, remove me */} @@ -112,12 +101,18 @@ class Input extends PureComponent { }) } + async componentWillUnmount () { + const conversation = this.props.navigation.getParam('conversation') + await this.props.screenProps.context.mutations.conversationRead({ + id: conversation.id, + }) + } + onSubmit = () => { const { input } = this.state this.setState({ input: '' }, async () => { try { const conversation = this.props.navigation.getParam('conversation') - console.log('conversation', conversation) await this.props.screenProps.context.mutations.conversationAddMessage({ conversation: { id: conversation.id, @@ -231,7 +226,7 @@ export default class Detail extends PureComponent { }, }, ])} - subscriptions={[subscriptions.conversationNewMessage]} + subscriptions={[subscriptions.newMessage]} fragment={fragments.EventList} alias='EventList' renderItem={props => ( diff --git a/client/react-native/common/components/Screens/Chats/List.js b/client/react-native/common/components/Screens/Chats/List.js index 7caab834a7..7877980106 100644 --- a/client/react-native/common/components/Screens/Chats/List.js +++ b/client/react-native/common/components/Screens/Chats/List.js @@ -11,7 +11,7 @@ import { conversation as utils } from '../../../utils' const Item = fragments.Conversation(({ data, navigation }) => { const { id, updatedAt, readAt } = data const isRead = new Date(readAt).getTime() > 0 - const isInvite = new Date(updatedAt).getTime() > 0 && !isRead + const isInvite = !isRead && new Date(updatedAt).getTime() <= 0 return ( { {utils.getTitle(data)} - {isInvite - ? 'New conversation' - : isRead - ? 'No new message' + {isRead + ? 'No new message' + : isInvite + ? 'New conversation' : 'You have a new message'} @@ -81,7 +81,11 @@ export default class ListScreen extends PureComponent { variables={queries.ConversationList.defaultVariables} fragment={fragments.ConversationList} alias='ConversationList' - subscriptions={[subscriptions.conversationInvite]} + subscriptions={[ + subscriptions.conversationInvite, + subscriptions.conversationNewMessage, + subscriptions.conversationRead, + ]} renderItem={props => } /> diff --git a/client/react-native/common/components/Screens/Contacts/Detail/PublicKey.js b/client/react-native/common/components/Screens/Contacts/Detail/PublicKey.js index 80284fe2bc..f572911291 100644 --- a/client/react-native/common/components/Screens/Contacts/Detail/PublicKey.js +++ b/client/react-native/common/components/Screens/Contacts/Detail/PublicKey.js @@ -16,7 +16,6 @@ export default class DetailPublicKey extends PureComponent { }) render () { - console.log(this.props) const id = this.props.navigation.getParam('id') const displayName = this.props.navigation.getParam('displayName') diff --git a/client/react-native/common/components/Screens/Contacts/List.js b/client/react-native/common/components/Screens/Contacts/List.js index 8f60bf45e0..fc19c8124d 100644 --- a/client/react-native/common/components/Screens/Contacts/List.js +++ b/client/react-native/common/components/Screens/Contacts/List.js @@ -76,7 +76,6 @@ export default class ContactList extends PureComponent { context: { queries, subscriptions }, }, } = this.props - console.log(this.context) return ( { - console.log(queries) const data = await queries.DeviceInfos.fetch() this.setState({ infos: data.infos, refreshing: false }) }) diff --git a/client/react-native/common/graphql/enums.gen.js b/client/react-native/common/graphql/enums.gen.js index 15aea915c9..bd4842b084 100644 --- a/client/react-native/common/graphql/enums.gen.js +++ b/client/react-native/common/graphql/enums.gen.js @@ -205,12 +205,14 @@ export const BertyP2pKindInputKind = { Sent: 101, Ack: 102, Ping: 103, + Seen: 104, ContactRequest: 201, ContactRequestAccepted: 202, ContactShareMe: 203, ContactShare: 204, ConversationInvite: 301, ConversationNewMessage: 302, + ConversationRead: 303, DevtoolsMapset: 401, SenderAliasUpdate: 501, Node: 99, @@ -221,12 +223,14 @@ export const ValueBertyP2pKindInputKind = { 101: 'Sent', 102: 'Ack', 103: 'Ping', + 104: 'Seen', 201: 'ContactRequest', 202: 'ContactRequestAccepted', 203: 'ContactShareMe', 204: 'ContactShare', 301: 'ConversationInvite', 302: 'ConversationNewMessage', + 303: 'ConversationRead', 401: 'DevtoolsMapset', 501: 'SenderAliasUpdate', 99: 'Node', diff --git a/client/react-native/common/graphql/mutations/ConversationCreate.js b/client/react-native/common/graphql/mutations/ConversationCreate.js index 26e5c3cbdb..9a2deec635 100644 --- a/client/react-native/common/graphql/mutations/ConversationCreate.js +++ b/client/react-native/common/graphql/mutations/ConversationCreate.js @@ -55,7 +55,6 @@ export default context => (input, configs) => input, { updater: (store, data) => - console.log('ConversationCreate', data) || context.updaters.conversationList.forEach(updater => updater(store, data.ConversationCreate) ), diff --git a/client/react-native/common/graphql/queries/Conversation.js b/client/react-native/common/graphql/queries/Conversation.js index 0c3170328f..6e2139271b 100644 --- a/client/react-native/common/graphql/queries/Conversation.js +++ b/client/react-native/common/graphql/queries/Conversation.js @@ -71,5 +71,5 @@ export default context => ({ context.environment, query, merge([defaultVariables, variables]) - )).GetConversation, + )).Conversation, }) diff --git a/client/react-native/common/graphql/subscriptions/ContactRequest.js b/client/react-native/common/graphql/subscriptions/ContactRequest.js index 3f21fe3de0..660ef15f14 100644 --- a/client/react-native/common/graphql/subscriptions/ContactRequest.js +++ b/client/react-native/common/graphql/subscriptions/ContactRequest.js @@ -12,7 +12,6 @@ export default context => ({ (async (store, data) => { if (data.EventStream.kind === 201) { const attributes = parseEmbedded(data.EventStream.attributes) - console.log(attributes) const id = btoa('contact:' + attributes.me.id) updater(store, { id }) await context.queries.Contact.fetch({ diff --git a/client/react-native/common/graphql/subscriptions/ConversationNewMessage.js b/client/react-native/common/graphql/subscriptions/ConversationNewMessage.js index e92421aca6..1dd43aa33f 100644 --- a/client/react-native/common/graphql/subscriptions/ConversationNewMessage.js +++ b/client/react-native/common/graphql/subscriptions/ConversationNewMessage.js @@ -8,7 +8,6 @@ export default context => ({ updater && (async (store, data) => { if (data.EventStream.kind === 302) { - updater(store, data.EventStream) await context.queries.Conversation.fetch({ id: data.EventStream.conversationId, }) diff --git a/client/react-native/common/graphql/subscriptions/ConversationRead.js b/client/react-native/common/graphql/subscriptions/ConversationRead.js new file mode 100644 index 0000000000..50e41f4d40 --- /dev/null +++ b/client/react-native/common/graphql/subscriptions/ConversationRead.js @@ -0,0 +1,23 @@ +import EventStream from './EventStream' + +export default context => ({ + ...EventStream(context), + subscribe: ({ updater }) => + EventStream(context).subscribe({ + updater: + updater && + (async (store, data) => { + if (data.EventStream.kind === 303) { + await context.queries.Conversation.fetch({ + id: data.EventStream.conversationId, + }) + await context.queries.EventList.fetch({ + filter: { + conversationId: data.EventStream.conversationId, + kind: 302, + }, + }) + } + }), + }), +}) diff --git a/client/react-native/common/graphql/subscriptions/EventSeen.js b/client/react-native/common/graphql/subscriptions/EventSeen.js new file mode 100644 index 0000000000..9c6a4eb9c4 --- /dev/null +++ b/client/react-native/common/graphql/subscriptions/EventSeen.js @@ -0,0 +1,39 @@ +import { parseEmbedded } from '../../helpers/json' +import EventStream from './EventStream' + +export default context => ({ + ...EventStream(context), + subscribe: ({ updater }) => + EventStream(context).subscribe({ + updater: + updater && + (async (store, data) => { + if (data.EventStream.kind === 104) { + const attributes = parseEmbedded(data.EventStream.attributes) + + // update all messages + const events = [] + attributes.ids && + attributes.ids.forEach(async id => { + try { + events.push(await context.queries.Event.fetch({ id })) + } catch (err) { + console.warn(err) + } + }) + + // update all conversations + events.reduce((conversations, event) => { + if (conversations[event.conversationId]) { + return conversations + } + const promise = context.queries.Conversation.fetch({ + id: data.EventStream.conversationId, + }) + conversations[event.conversationId] = promise + return conversations + }, {}) + } + }), + }), +}) diff --git a/client/react-native/common/graphql/subscriptions/EventStream.js b/client/react-native/common/graphql/subscriptions/EventStream.js index a7b553dfb7..ffce99915e 100644 --- a/client/react-native/common/graphql/subscriptions/EventStream.js +++ b/client/react-native/common/graphql/subscriptions/EventStream.js @@ -25,11 +25,10 @@ const EventStream = graphql` } ` -let _context = null let _subscriber = null export default context => { - if (subscriber === null || context !== _context) { + if (_subscriber === null) { _subscriber = subscriber({ environment: context.environment, subscription: EventStream, diff --git a/client/react-native/common/graphql/subscriptions/NewMessage.js b/client/react-native/common/graphql/subscriptions/NewMessage.js new file mode 100644 index 0000000000..6244584819 --- /dev/null +++ b/client/react-native/common/graphql/subscriptions/NewMessage.js @@ -0,0 +1,15 @@ +import EventStream from './EventStream' + +export default context => ({ + ...EventStream(context), + subscribe: ({ updater }) => + EventStream(context).subscribe({ + updater: + updater && + (async (store, data) => { + if (data.EventStream.kind === 302) { + updater(store, data.EventStream) + } + }), + }), +}) diff --git a/client/react-native/common/graphql/subscriptions/index.js b/client/react-native/common/graphql/subscriptions/index.js index bba38772b3..00db479056 100644 --- a/client/react-native/common/graphql/subscriptions/index.js +++ b/client/react-native/common/graphql/subscriptions/index.js @@ -1,7 +1,10 @@ export eventStream from './EventStream' +export eventSeen from './EventSeen' export contactRequest from './ContactRequest' export contactRequestAccepted from './ContactRequestAccepted' export conversationInvite from './ConversationInvite' export conversationNewMessage from './ConversationNewMessage' +export conversationRead from './ConversationRead' +export newMessage from './NewMessage' export monitorPeers from './MonitorPeers' export logStream from './LogStream' diff --git a/client/react-native/common/relay/RelayContext.js b/client/react-native/common/relay/RelayContext.js index b60f523b77..c29fa28b81 100644 --- a/client/react-native/common/relay/RelayContext.js +++ b/client/react-native/common/relay/RelayContext.js @@ -31,5 +31,4 @@ export const contextValue = ({ } const RelayContext = React.createContext() -console.log(RelayContext) export default RelayContext diff --git a/client/react-native/common/relay/environment.js b/client/react-native/common/relay/environment.js index f8628587c9..8a9f9b7105 100644 --- a/client/react-native/common/relay/environment.js +++ b/client/react-native/common/relay/environment.js @@ -111,8 +111,6 @@ const setupMiddlewares = async ({ getIp, getPort }) => [ ] export const setup = async ({ getIp, getPort }) => { - const store = new Store(new RecordSource()) - console.log(store, store.getSource()) return new Environment({ network: new RelayNetworkLayer(await setupMiddlewares({ getIp, getPort }), { subscribeFn: setupSubscription({ diff --git a/client/react-native/common/relay/genericUpdater.js b/client/react-native/common/relay/genericUpdater.js index 34f6d63ba3..0762d974a9 100644 --- a/client/react-native/common/relay/genericUpdater.js +++ b/client/react-native/common/relay/genericUpdater.js @@ -33,7 +33,6 @@ export default (fragment, alias, args) => { return (store, data, deletion) => { const helper = new FragmentHelper(fragment) const connectionHelper = helper.getConnection(alias) - console.log(alias, data, args) const root = store.getRoot() @@ -76,11 +75,7 @@ export default (fragment, alias, args) => { if ( edges.length > 0 && - edges.some( - e => - console.log(e.getLinkedRecord('node')) || - e.getLinkedRecord('node').getValue('id') === data.id - ) + edges.some(e => e.getLinkedRecord('node').getValue('id') === data.id) ) { // update return diff --git a/client/react-native/common/schema.graphql b/client/react-native/common/schema.graphql index b5887972ea..0af1ad0c1d 100644 --- a/client/react-native/common/schema.graphql +++ b/client/react-native/common/schema.graphql @@ -356,6 +356,9 @@ type BertyP2pAckAttrs { type BertyP2pPingAttrs { T: Bool! } +type BertyP2pSeenAttrs { + ids: [String!] +} type BertyP2pContactRequestAttrs { me: BertyEntityContact introText: String! @@ -375,6 +378,9 @@ type BertyP2pConversationInviteAttrs { type BertyP2pConversationNewMessageAttrs { message: BertyEntityMessage } +type BertyP2pConversationReadAttrs { + conversation: BertyEntityConversation +} type BertyP2pDevtoolsMapsetAttrs { key: String! val: String! diff --git a/core/api/node/graphql/enums.gen.js b/core/api/node/graphql/enums.gen.js index 15aea915c9..bd4842b084 100644 --- a/core/api/node/graphql/enums.gen.js +++ b/core/api/node/graphql/enums.gen.js @@ -205,12 +205,14 @@ export const BertyP2pKindInputKind = { Sent: 101, Ack: 102, Ping: 103, + Seen: 104, ContactRequest: 201, ContactRequestAccepted: 202, ContactShareMe: 203, ContactShare: 204, ConversationInvite: 301, ConversationNewMessage: 302, + ConversationRead: 303, DevtoolsMapset: 401, SenderAliasUpdate: 501, Node: 99, @@ -221,12 +223,14 @@ export const ValueBertyP2pKindInputKind = { 101: 'Sent', 102: 'Ack', 103: 'Ping', + 104: 'Seen', 201: 'ContactRequest', 202: 'ContactRequestAccepted', 203: 'ContactShareMe', 204: 'ContactShare', 301: 'ConversationInvite', 302: 'ConversationNewMessage', + 303: 'ConversationRead', 401: 'DevtoolsMapset', 501: 'SenderAliasUpdate', 99: 'Node', diff --git a/core/api/node/graphql/gqlgen.gen.yml b/core/api/node/graphql/gqlgen.gen.yml index 23cc189a2a..700f1b006c 100644 --- a/core/api/node/graphql/gqlgen.gen.yml +++ b/core/api/node/graphql/gqlgen.gen.yml @@ -1092,6 +1092,19 @@ models: fields: t: + BertyP2pSeenAttrs: + model: berty.tech/core/api/p2p.SeenAttrs + fields: + ids: + BertyP2pSeenAttrsInput: + model: berty.tech/core/api/p2p.SeenAttrs + fields: + ids: + BertyP2pSeenAttrsPayload: + model: berty.tech/core/api/p2p.SeenAttrs + fields: + ids: + BertyP2pContactRequestAttrs: model: berty.tech/core/api/p2p.ContactRequestAttrs fields: @@ -1173,6 +1186,19 @@ models: fields: message: + BertyP2pConversationReadAttrs: + model: berty.tech/core/api/p2p.ConversationReadAttrs + fields: + conversation: + BertyP2pConversationReadAttrsInput: + model: berty.tech/core/api/p2p.ConversationReadAttrs + fields: + conversation: + BertyP2pConversationReadAttrsPayload: + model: berty.tech/core/api/p2p.ConversationReadAttrs + fields: + conversation: + BertyP2pDevtoolsMapsetAttrs: model: berty.tech/core/api/p2p.DevtoolsMapsetAttrs fields: diff --git a/core/api/node/graphql/graph/generated/generated.gen.go b/core/api/node/graphql/graph/generated/generated.gen.go index 1ab650182d..f08fd3833b 100644 --- a/core/api/node/graphql/graph/generated/generated.gen.go +++ b/core/api/node/graphql/graph/generated/generated.gen.go @@ -303,6 +303,10 @@ type ComplexityRoot struct { Message func(childComplexity int) int } + BertyP2pConversationReadAttrs struct { + Conversation func(childComplexity int) int + } + BertyP2pDevtoolsMapsetAttrs struct { Key func(childComplexity int) int Val func(childComplexity int) int @@ -341,6 +345,10 @@ type ComplexityRoot struct { T func(childComplexity int) int } + BertyP2pSeenAttrs struct { + Ids func(childComplexity int) int + } + BertyP2pSenderAliasUpdateAttrs struct { Aliases func(childComplexity int) int } @@ -3142,6 +3150,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.BertyP2pConversationNewMessageAttrs.Message(childComplexity), true + case "BertyP2pConversationReadAttrs.conversation": + if e.complexity.BertyP2pConversationReadAttrs.Conversation == nil { + break + } + + return e.complexity.BertyP2pConversationReadAttrs.Conversation(childComplexity), true + case "BertyP2pDevtoolsMapsetAttrs.key": if e.complexity.BertyP2pDevtoolsMapsetAttrs.Key == nil { break @@ -11617,6 +11632,62 @@ func (ec *executionContext) _BertyP2pPingAttrs_T(ctx context.Context, field grap return models.MarshalBool(res) } +var bertyP2pSeenAttrsImplementors = []string{"BertyP2pSeenAttrs"} + +// nolint: gocyclo, errcheck, gas, goconst +func (ec *executionContext) _BertyP2pSeenAttrs(ctx context.Context, sel ast.SelectionSet, obj *p2p.SeenAttrs) graphql.Marshaler { + fields := graphql.CollectFields(ctx, sel, bertyP2pSeenAttrsImplementors) + + out := graphql.NewOrderedMap(len(fields)) + invalid := false + for i, field := range fields { + out.Keys[i] = field.Alias + + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("BertyP2pSeenAttrs") + case "ids": + out.Values[i] = ec._BertyP2pSeenAttrs_ids(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + + if invalid { + return graphql.Null + } + return out +} + +// nolint: vetshadow +func (ec *executionContext) _BertyP2pSeenAttrs_ids(ctx context.Context, field graphql.CollectedField, obj *p2p.SeenAttrs) graphql.Marshaler { + rctx := &graphql.ResolverContext{ + Object: "BertyP2pSeenAttrs", + Args: nil, + Field: field, + } + ctx = graphql.WithResolverContext(ctx, rctx) + resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IDs, nil + }) + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]string) + rctx.Result = res + + arr1 := make(graphql.Array, len(res)) + + for idx1 := range res { + arr1[idx1] = func() graphql.Marshaler { + return models.MarshalString(res[idx1]) + }() + } + + return arr1 +} + var bertyP2pSenderAliasUpdateAttrsImplementors = []string{"BertyP2pSenderAliasUpdateAttrs"} // nolint: gocyclo, errcheck, gas, goconst @@ -21255,6 +21326,9 @@ type BertyP2pAckAttrs { type BertyP2pPingAttrs { T: Bool! } +type BertyP2pSeenAttrs { + ids: [String!] +} type BertyP2pContactRequestAttrs { me: BertyEntityContact introText: String! @@ -21274,6 +21348,9 @@ type BertyP2pConversationInviteAttrs { type BertyP2pConversationNewMessageAttrs { message: BertyEntityMessage } +type BertyP2pConversationReadAttrs { + conversation: BertyEntityConversation +} type BertyP2pDevtoolsMapsetAttrs { key: String! val: String! diff --git a/core/api/node/graphql/service.gen.graphql b/core/api/node/graphql/service.gen.graphql index b5887972ea..0af1ad0c1d 100644 --- a/core/api/node/graphql/service.gen.graphql +++ b/core/api/node/graphql/service.gen.graphql @@ -356,6 +356,9 @@ type BertyP2pAckAttrs { type BertyP2pPingAttrs { T: Bool! } +type BertyP2pSeenAttrs { + ids: [String!] +} type BertyP2pContactRequestAttrs { me: BertyEntityContact introText: String! @@ -375,6 +378,9 @@ type BertyP2pConversationInviteAttrs { type BertyP2pConversationNewMessageAttrs { message: BertyEntityMessage } +type BertyP2pConversationReadAttrs { + conversation: BertyEntityConversation +} type BertyP2pDevtoolsMapsetAttrs { key: String! val: String! diff --git a/core/api/p2p/kind.gen.go b/core/api/p2p/kind.gen.go index e50a6557cd..061ff490db 100644 --- a/core/api/p2p/kind.gen.go +++ b/core/api/p2p/kind.gen.go @@ -65,6 +65,25 @@ func (e *Event) SetPingAttrs(attrs *PingAttrs) error { return nil } +// GetSeenAttrs is a typesafe version of GetAttrs +func (e *Event) GetSeenAttrs() (*SeenAttrs, error) { + if e.Attributes == nil || len(e.Attributes) == 0 { + return &SeenAttrs{}, nil + } + var attrs SeenAttrs + return &attrs, proto.Unmarshal(e.Attributes, &attrs) +} + +// SetSeenAttrs is a typesafe version of the generic SetAttrs method +func (e *Event) SetSeenAttrs(attrs *SeenAttrs) error { + raw, err := proto.Marshal(attrs) + if err != nil { + return err + } + e.Attributes = raw + return nil +} + // GetContactRequestAttrs is a typesafe version of GetAttrs func (e *Event) GetContactRequestAttrs() (*ContactRequestAttrs, error) { if e.Attributes == nil || len(e.Attributes) == 0 { @@ -179,6 +198,25 @@ func (e *Event) SetConversationNewMessageAttrs(attrs *ConversationNewMessageAttr return nil } +// GetConversationReadAttrs is a typesafe version of GetAttrs +func (e *Event) GetConversationReadAttrs() (*ConversationReadAttrs, error) { + if e.Attributes == nil || len(e.Attributes) == 0 { + return &ConversationReadAttrs{}, nil + } + var attrs ConversationReadAttrs + return &attrs, proto.Unmarshal(e.Attributes, &attrs) +} + +// SetConversationReadAttrs is a typesafe version of the generic SetAttrs method +func (e *Event) SetConversationReadAttrs(attrs *ConversationReadAttrs) error { + raw, err := proto.Marshal(attrs) + if err != nil { + return err + } + e.Attributes = raw + return nil +} + // GetDevtoolsMapsetAttrs is a typesafe version of GetAttrs func (e *Event) GetDevtoolsMapsetAttrs() (*DevtoolsMapsetAttrs, error) { if e.Attributes == nil || len(e.Attributes) == 0 { @@ -245,6 +283,8 @@ func (e *Event) GetAttrs() (proto.Message, error) { return e.GetAckAttrs() case Kind_Ping: return e.GetPingAttrs() + case Kind_Seen: + return e.GetSeenAttrs() case Kind_ContactRequest: return e.GetContactRequestAttrs() case Kind_ContactRequestAccepted: @@ -257,6 +297,8 @@ func (e *Event) GetAttrs() (proto.Message, error) { return e.GetConversationInviteAttrs() case Kind_ConversationNewMessage: return e.GetConversationNewMessageAttrs() + case Kind_ConversationRead: + return e.GetConversationReadAttrs() case Kind_DevtoolsMapset: return e.GetDevtoolsMapsetAttrs() case Kind_SenderAliasUpdate: diff --git a/core/api/p2p/kind.pb.go b/core/api/p2p/kind.pb.go index 153b431b12..db2adc1bde 100644 --- a/core/api/p2p/kind.pb.go +++ b/core/api/p2p/kind.pb.go @@ -32,13 +32,16 @@ const ( // Ack events are created and sent after receiving an event from a peer. Kind_Ack Kind = 102 // Ping events can be use to check and measure the availability of a peer. - Kind_Ping Kind = 103 + Kind_Ping Kind = 103 + // Seen events are sent by frontend + Kind_Seen Kind = 104 Kind_ContactRequest Kind = 201 Kind_ContactRequestAccepted Kind = 202 Kind_ContactShareMe Kind = 203 Kind_ContactShare Kind = 204 Kind_ConversationInvite Kind = 301 Kind_ConversationNewMessage Kind = 302 + Kind_ConversationRead Kind = 303 // Devtool events Kind_DevtoolsMapset Kind = 401 Kind_SenderAliasUpdate Kind = 501 @@ -52,12 +55,14 @@ var Kind_name = map[int32]string{ 101: "Sent", 102: "Ack", 103: "Ping", + 104: "Seen", 201: "ContactRequest", 202: "ContactRequestAccepted", 203: "ContactShareMe", 204: "ContactShare", 301: "ConversationInvite", 302: "ConversationNewMessage", + 303: "ConversationRead", 401: "DevtoolsMapset", 501: "SenderAliasUpdate", 99: "Node", @@ -67,12 +72,14 @@ var Kind_value = map[string]int32{ "Sent": 101, "Ack": 102, "Ping": 103, + "Seen": 104, "ContactRequest": 201, "ContactRequestAccepted": 202, "ContactShareMe": 203, "ContactShare": 204, "ConversationInvite": 301, "ConversationNewMessage": 302, + "ConversationRead": 303, "DevtoolsMapset": 401, "SenderAliasUpdate": 501, "Node": 99, @@ -82,7 +89,7 @@ func (x Kind) String() string { return proto.EnumName(Kind_name, int32(x)) } func (Kind) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{0} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{0} } type SentAttrs struct { @@ -96,7 +103,7 @@ func (m *SentAttrs) Reset() { *m = SentAttrs{} } func (m *SentAttrs) String() string { return proto.CompactTextString(m) } func (*SentAttrs) ProtoMessage() {} func (*SentAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{0} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{0} } func (m *SentAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -144,7 +151,7 @@ func (m *AckAttrs) Reset() { *m = AckAttrs{} } func (m *AckAttrs) String() string { return proto.CompactTextString(m) } func (*AckAttrs) ProtoMessage() {} func (*AckAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{1} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{1} } func (m *AckAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -198,7 +205,7 @@ func (m *PingAttrs) Reset() { *m = PingAttrs{} } func (m *PingAttrs) String() string { return proto.CompactTextString(m) } func (*PingAttrs) ProtoMessage() {} func (*PingAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{2} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{2} } func (m *PingAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -234,6 +241,53 @@ func (m *PingAttrs) GetT() bool { return false } +type SeenAttrs struct { + IDs []string `protobuf:"bytes,1,rep,name=ids" json:"ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SeenAttrs) Reset() { *m = SeenAttrs{} } +func (m *SeenAttrs) String() string { return proto.CompactTextString(m) } +func (*SeenAttrs) ProtoMessage() {} +func (*SeenAttrs) Descriptor() ([]byte, []int) { + return fileDescriptor_kind_e5d433b2b1d12cba, []int{3} +} +func (m *SeenAttrs) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SeenAttrs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SeenAttrs.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (dst *SeenAttrs) XXX_Merge(src proto.Message) { + xxx_messageInfo_SeenAttrs.Merge(dst, src) +} +func (m *SeenAttrs) XXX_Size() int { + return m.Size() +} +func (m *SeenAttrs) XXX_DiscardUnknown() { + xxx_messageInfo_SeenAttrs.DiscardUnknown(m) +} + +var xxx_messageInfo_SeenAttrs proto.InternalMessageInfo + +func (m *SeenAttrs) GetIDs() []string { + if m != nil { + return m.IDs + } + return nil +} + type ContactRequestAttrs struct { Me *entity.Contact `protobuf:"bytes,1,opt,name=me" json:"me,omitempty"` IntroText string `protobuf:"bytes,2,opt,name=intro_text,json=introText,proto3" json:"intro_text,omitempty"` @@ -246,7 +300,7 @@ func (m *ContactRequestAttrs) Reset() { *m = ContactRequestAttrs{} } func (m *ContactRequestAttrs) String() string { return proto.CompactTextString(m) } func (*ContactRequestAttrs) ProtoMessage() {} func (*ContactRequestAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{3} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{4} } func (m *ContactRequestAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -300,7 +354,7 @@ func (m *ContactRequestAcceptedAttrs) Reset() { *m = ContactRequestAccep func (m *ContactRequestAcceptedAttrs) String() string { return proto.CompactTextString(m) } func (*ContactRequestAcceptedAttrs) ProtoMessage() {} func (*ContactRequestAcceptedAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{4} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{5} } func (m *ContactRequestAcceptedAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -347,7 +401,7 @@ func (m *ContactShareMeAttrs) Reset() { *m = ContactShareMeAttrs{} } func (m *ContactShareMeAttrs) String() string { return proto.CompactTextString(m) } func (*ContactShareMeAttrs) ProtoMessage() {} func (*ContactShareMeAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{5} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{6} } func (m *ContactShareMeAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -394,7 +448,7 @@ func (m *ContactShareAttrs) Reset() { *m = ContactShareAttrs{} } func (m *ContactShareAttrs) String() string { return proto.CompactTextString(m) } func (*ContactShareAttrs) ProtoMessage() {} func (*ContactShareAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{6} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{7} } func (m *ContactShareAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -441,7 +495,7 @@ func (m *ConversationInviteAttrs) Reset() { *m = ConversationInviteAttrs func (m *ConversationInviteAttrs) String() string { return proto.CompactTextString(m) } func (*ConversationInviteAttrs) ProtoMessage() {} func (*ConversationInviteAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{7} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{8} } func (m *ConversationInviteAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -488,7 +542,7 @@ func (m *ConversationNewMessageAttrs) Reset() { *m = ConversationNewMess func (m *ConversationNewMessageAttrs) String() string { return proto.CompactTextString(m) } func (*ConversationNewMessageAttrs) ProtoMessage() {} func (*ConversationNewMessageAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{8} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{9} } func (m *ConversationNewMessageAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -524,6 +578,53 @@ func (m *ConversationNewMessageAttrs) GetMessage() *entity.Message { return nil } +type ConversationReadAttrs struct { + Conversation *entity.Conversation `protobuf:"bytes,1,opt,name=conversation" json:"conversation,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ConversationReadAttrs) Reset() { *m = ConversationReadAttrs{} } +func (m *ConversationReadAttrs) String() string { return proto.CompactTextString(m) } +func (*ConversationReadAttrs) ProtoMessage() {} +func (*ConversationReadAttrs) Descriptor() ([]byte, []int) { + return fileDescriptor_kind_e5d433b2b1d12cba, []int{10} +} +func (m *ConversationReadAttrs) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConversationReadAttrs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConversationReadAttrs.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (dst *ConversationReadAttrs) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConversationReadAttrs.Merge(dst, src) +} +func (m *ConversationReadAttrs) XXX_Size() int { + return m.Size() +} +func (m *ConversationReadAttrs) XXX_DiscardUnknown() { + xxx_messageInfo_ConversationReadAttrs.DiscardUnknown(m) +} + +var xxx_messageInfo_ConversationReadAttrs proto.InternalMessageInfo + +func (m *ConversationReadAttrs) GetConversation() *entity.Conversation { + if m != nil { + return m.Conversation + } + return nil +} + type DevtoolsMapsetAttrs struct { Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Val string `protobuf:"bytes,2,opt,name=val,proto3" json:"val,omitempty"` @@ -536,7 +637,7 @@ func (m *DevtoolsMapsetAttrs) Reset() { *m = DevtoolsMapsetAttrs{} } func (m *DevtoolsMapsetAttrs) String() string { return proto.CompactTextString(m) } func (*DevtoolsMapsetAttrs) ProtoMessage() {} func (*DevtoolsMapsetAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{9} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{11} } func (m *DevtoolsMapsetAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -590,7 +691,7 @@ func (m *SenderAliasUpdateAttrs) Reset() { *m = SenderAliasUpdateAttrs{} func (m *SenderAliasUpdateAttrs) String() string { return proto.CompactTextString(m) } func (*SenderAliasUpdateAttrs) ProtoMessage() {} func (*SenderAliasUpdateAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{10} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{12} } func (m *SenderAliasUpdateAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -640,7 +741,7 @@ func (m *NodeAttrs) Reset() { *m = NodeAttrs{} } func (m *NodeAttrs) String() string { return proto.CompactTextString(m) } func (*NodeAttrs) ProtoMessage() {} func (*NodeAttrs) Descriptor() ([]byte, []int) { - return fileDescriptor_kind_c3e540c872dae711, []int{11} + return fileDescriptor_kind_e5d433b2b1d12cba, []int{13} } func (m *NodeAttrs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -687,12 +788,14 @@ func init() { proto.RegisterType((*SentAttrs)(nil), "berty.p2p.SentAttrs") proto.RegisterType((*AckAttrs)(nil), "berty.p2p.AckAttrs") proto.RegisterType((*PingAttrs)(nil), "berty.p2p.PingAttrs") + proto.RegisterType((*SeenAttrs)(nil), "berty.p2p.SeenAttrs") proto.RegisterType((*ContactRequestAttrs)(nil), "berty.p2p.ContactRequestAttrs") proto.RegisterType((*ContactRequestAcceptedAttrs)(nil), "berty.p2p.ContactRequestAcceptedAttrs") proto.RegisterType((*ContactShareMeAttrs)(nil), "berty.p2p.ContactShareMeAttrs") proto.RegisterType((*ContactShareAttrs)(nil), "berty.p2p.ContactShareAttrs") proto.RegisterType((*ConversationInviteAttrs)(nil), "berty.p2p.ConversationInviteAttrs") proto.RegisterType((*ConversationNewMessageAttrs)(nil), "berty.p2p.ConversationNewMessageAttrs") + proto.RegisterType((*ConversationReadAttrs)(nil), "berty.p2p.ConversationReadAttrs") proto.RegisterType((*DevtoolsMapsetAttrs)(nil), "berty.p2p.DevtoolsMapsetAttrs") proto.RegisterType((*SenderAliasUpdateAttrs)(nil), "berty.p2p.SenderAliasUpdateAttrs") proto.RegisterType((*NodeAttrs)(nil), "berty.p2p.NodeAttrs") @@ -807,6 +910,42 @@ func (m *PingAttrs) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *SeenAttrs) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SeenAttrs) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.IDs) > 0 { + for _, s := range m.IDs { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } + return i, nil +} + func (m *ContactRequestAttrs) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -999,6 +1138,37 @@ func (m *ConversationNewMessageAttrs) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *ConversationReadAttrs) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConversationReadAttrs) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Conversation != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintKind(dAtA, i, uint64(m.Conversation.Size())) + n6, err := m.Conversation.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } + return i, nil +} + func (m *DevtoolsMapsetAttrs) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1161,6 +1331,24 @@ func (m *PingAttrs) Size() (n int) { return n } +func (m *SeenAttrs) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.IDs) > 0 { + for _, s := range m.IDs { + l = len(s) + n += 1 + l + sovKind(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *ContactRequestAttrs) Size() (n int) { if m == nil { return 0 @@ -1260,6 +1448,22 @@ func (m *ConversationNewMessageAttrs) Size() (n int) { return n } +func (m *ConversationReadAttrs) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Conversation != nil { + l = m.Conversation.Size() + n += 1 + l + sovKind(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *DevtoolsMapsetAttrs) Size() (n int) { if m == nil { return 0 @@ -1590,6 +1794,86 @@ func (m *PingAttrs) Unmarshal(dAtA []byte) error { } return nil } +func (m *SeenAttrs) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKind + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SeenAttrs: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SeenAttrs: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IDs", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKind + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthKind + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.IDs = append(m.IDs, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipKind(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthKind + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ContactRequestAttrs) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -2110,6 +2394,90 @@ func (m *ConversationNewMessageAttrs) Unmarshal(dAtA []byte) error { } return nil } +func (m *ConversationReadAttrs) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKind + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConversationReadAttrs: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConversationReadAttrs: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Conversation", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKind + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthKind + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Conversation == nil { + m.Conversation = &entity.Conversation{} + } + if err := m.Conversation.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipKind(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthKind + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DevtoolsMapsetAttrs) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -2507,47 +2875,49 @@ var ( ErrIntOverflowKind = fmt.Errorf("proto: integer overflow") ) -func init() { proto.RegisterFile("api/p2p/kind.proto", fileDescriptor_kind_c3e540c872dae711) } - -var fileDescriptor_kind_c3e540c872dae711 = []byte{ - // 618 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0xed, 0xc4, 0xfd, 0x9a, 0xfa, 0x36, 0xfa, 0x34, 0x9d, 0x42, 0xd3, 0xb4, 0x22, 0x54, 0x96, - 0x40, 0x05, 0xa4, 0x58, 0x4a, 0x57, 0x48, 0xa8, 0x28, 0x6d, 0x37, 0x15, 0x4a, 0x85, 0xdc, 0x76, - 0x01, 0x2c, 0x2a, 0xd7, 0xbe, 0xb8, 0x56, 0x1a, 0x8f, 0x99, 0x99, 0xa6, 0xed, 0x63, 0xb0, 0xe3, - 0x25, 0xe0, 0x39, 0xf8, 0x5b, 0xf0, 0x04, 0x08, 0x85, 0x67, 0x40, 0x6c, 0xd1, 0xd8, 0xe3, 0xe0, - 0x34, 0x11, 0x88, 0xdd, 0xcd, 0xc9, 0xb9, 0xc7, 0xe7, 0xde, 0x7b, 0x6c, 0x60, 0x7e, 0x1a, 0xbb, - 0x69, 0x3b, 0x75, 0x7b, 0x71, 0x12, 0xb6, 0x52, 0xc1, 0x15, 0x67, 0xf6, 0x09, 0x0a, 0x75, 0xd5, - 0x4a, 0xdb, 0xe9, 0xea, 0x0d, 0x4c, 0x54, 0xac, 0xae, 0xdc, 0x80, 0x27, 0xca, 0x0f, 0x54, 0x4e, - 0x58, 0x6d, 0xfc, 0x46, 0x07, 0x28, 0xa4, 0xaf, 0x62, 0x9e, 0x98, 0xbf, 0x8a, 0x86, 0x3e, 0x4a, - 0xe9, 0x47, 0x78, 0xad, 0x41, 0x62, 0x12, 0xa2, 0x38, 0xf6, 0xcf, 0x62, 0x5f, 0x16, 0x0d, 0x11, - 0x8f, 0x78, 0x56, 0xba, 0xba, 0xca, 0x51, 0xe7, 0x2e, 0xd8, 0x07, 0x98, 0xa8, 0x8e, 0x52, 0x42, - 0xb2, 0x06, 0x58, 0x71, 0x28, 0x57, 0xc8, 0xba, 0xb5, 0x61, 0x6f, 0x57, 0x87, 0x5f, 0x6f, 0x5b, - 0x7b, 0xbb, 0xd2, 0xd3, 0x98, 0xb3, 0x05, 0xf3, 0x9d, 0xa0, 0xf7, 0x37, 0x1a, 0xab, 0x43, 0x15, - 0x85, 0x38, 0xee, 0xcb, 0x68, 0xa5, 0xb2, 0x4e, 0x36, 0x6c, 0x6f, 0x0e, 0x85, 0xe8, 0xca, 0xc8, - 0x69, 0x80, 0xfd, 0x34, 0x4e, 0xa2, 0x5c, 0xa0, 0x06, 0xe4, 0x70, 0x85, 0xac, 0x93, 0x8d, 0x79, - 0x8f, 0x1c, 0x3a, 0x2f, 0x60, 0x69, 0x27, 0x9f, 0xda, 0xc3, 0x57, 0xe7, 0x28, 0x8d, 0x99, 0x3b, - 0x50, 0xe9, 0x63, 0xc6, 0x5a, 0x68, 0xdf, 0x6c, 0xe5, 0x9b, 0xca, 0xa7, 0x6b, 0x15, 0xf4, 0x4a, - 0x1f, 0xd9, 0x2d, 0x80, 0x38, 0x51, 0x82, 0x1f, 0x2b, 0xbc, 0x54, 0xe6, 0xa1, 0x76, 0x86, 0x1c, - 0xe2, 0xa5, 0x72, 0x1e, 0xc0, 0xda, 0x35, 0xf1, 0x20, 0xc0, 0x54, 0x61, 0x38, 0xcd, 0xc9, 0xa3, - 0x91, 0x93, 0x83, 0x53, 0x5f, 0x60, 0x17, 0xff, 0xc5, 0x89, 0xb3, 0x0b, 0x8b, 0xe5, 0xee, 0xbc, - 0xd7, 0x85, 0xaa, 0x39, 0xe9, 0x9f, 0x05, 0x0a, 0x96, 0xf3, 0x0c, 0xea, 0x3b, 0xa5, 0x6b, 0xef, - 0x25, 0x83, 0x58, 0x19, 0xad, 0x2d, 0xa8, 0x95, 0x83, 0x60, 0x04, 0x57, 0x27, 0x04, 0x47, 0x0c, - 0x6f, 0x8c, 0xef, 0xec, 0x67, 0xbb, 0x18, 0xfd, 0xde, 0xc7, 0x8b, 0x6e, 0x1e, 0x9e, 0x91, 0x55, - 0x13, 0xa6, 0xe9, 0x56, 0x0d, 0xd9, 0x2b, 0x58, 0xce, 0x43, 0x58, 0xda, 0xc5, 0x81, 0xe2, 0xfc, - 0x4c, 0x76, 0xfd, 0x54, 0xa2, 0x39, 0x1c, 0x05, 0xab, 0x87, 0x57, 0x99, 0x86, 0xed, 0xe9, 0x52, - 0x23, 0x03, 0xff, 0xcc, 0x1c, 0x47, 0x97, 0x4e, 0x17, 0x96, 0x0f, 0xb2, 0x88, 0x76, 0x74, 0x42, - 0x8f, 0xd2, 0xd0, 0x2f, 0x86, 0xdc, 0x84, 0x6a, 0x96, 0x5a, 0xcc, 0x03, 0xb6, 0xd0, 0x6e, 0x8c, - 0xbb, 0x28, 0xb5, 0x79, 0x05, 0xd3, 0x79, 0x0c, 0xf6, 0x3e, 0x0f, 0x8d, 0x02, 0x83, 0x59, 0xfd, - 0x8e, 0x65, 0x06, 0xfe, 0xf3, 0xb2, 0x9a, 0x35, 0x01, 0x7c, 0xa5, 0x44, 0x7c, 0x72, 0xae, 0x50, - 0x66, 0x46, 0x6a, 0x5e, 0x09, 0xb9, 0xff, 0x93, 0xc0, 0xec, 0x13, 0x4d, 0x5c, 0x80, 0xea, 0x51, - 0xd2, 0x4b, 0xf8, 0x45, 0x42, 0x67, 0xd8, 0x3c, 0xcc, 0xea, 0x97, 0x83, 0x22, 0xab, 0x82, 0xd5, - 0x09, 0x7a, 0xf4, 0xa5, 0x86, 0x74, 0x8e, 0x69, 0xc4, 0x96, 0xe0, 0xff, 0xf1, 0x64, 0xd1, 0x0f, - 0x84, 0xad, 0xc1, 0xf2, 0xf4, 0xb8, 0xd1, 0x8f, 0xa4, 0xd4, 0x61, 0xe2, 0x45, 0x3f, 0x11, 0xb6, - 0x08, 0xb5, 0x32, 0x48, 0x3f, 0x13, 0x56, 0x07, 0x36, 0x19, 0x01, 0xfa, 0xb6, 0x62, 0xd4, 0xa7, - 0x1c, 0x90, 0xbe, 0xab, 0x68, 0xf5, 0xf1, 0x6b, 0xd0, 0xd7, 0x16, 0x5b, 0x86, 0xc5, 0x89, 0x3d, - 0xd3, 0x1f, 0x96, 0x1e, 0x43, 0x2f, 0x8c, 0x06, 0xdb, 0xf7, 0xde, 0x0f, 0x9b, 0xe4, 0xcb, 0xb0, - 0x49, 0xbe, 0x0d, 0x9b, 0xe4, 0xcd, 0xf7, 0xe6, 0xcc, 0xf3, 0x7a, 0xbe, 0x6f, 0x85, 0xc1, 0xa9, - 0x1b, 0x70, 0x81, 0xae, 0xf9, 0x72, 0x9d, 0xcc, 0x65, 0x9f, 0x8c, 0xcd, 0x5f, 0x01, 0x00, 0x00, - 0xff, 0xff, 0xc4, 0x72, 0x94, 0x32, 0xcb, 0x04, 0x00, 0x00, +func init() { proto.RegisterFile("api/p2p/kind.proto", fileDescriptor_kind_e5d433b2b1d12cba) } + +var fileDescriptor_kind_e5d433b2b1d12cba = []byte{ + // 645 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xdd, 0x4e, 0xd4, 0x40, + 0x14, 0xa6, 0x2d, 0xb2, 0xf4, 0xb0, 0x31, 0xc3, 0x20, 0x2c, 0x0b, 0x71, 0x25, 0x4d, 0x34, 0xa8, + 0xc9, 0x36, 0x59, 0xae, 0x4c, 0x0c, 0x66, 0x81, 0x1b, 0x62, 0x96, 0x98, 0x02, 0x31, 0xea, 0x05, + 0x29, 0xed, 0xb1, 0x34, 0xcb, 0x4e, 0xeb, 0xcc, 0xb0, 0xc0, 0x63, 0x78, 0xc7, 0x4b, 0xa8, 0xaf, + 0xe1, 0xdf, 0x85, 0x4f, 0x60, 0xcc, 0xfa, 0x0c, 0xde, 0x9b, 0x69, 0xa7, 0x6b, 0x17, 0x36, 0x12, + 0x13, 0xef, 0xce, 0x7e, 0xfd, 0xce, 0xd7, 0x6f, 0xcf, 0xf9, 0x4e, 0x81, 0xfa, 0x69, 0xec, 0xa6, + 0xad, 0xd4, 0xed, 0xc6, 0x2c, 0x6c, 0xa6, 0x3c, 0x91, 0x09, 0xb5, 0x0f, 0x91, 0xcb, 0xf3, 0x66, + 0xda, 0x4a, 0x97, 0x6e, 0x21, 0x93, 0xb1, 0x3c, 0x77, 0x83, 0x84, 0x49, 0x3f, 0x90, 0x39, 0x61, + 0xa9, 0xfe, 0x07, 0xed, 0x23, 0x17, 0xbe, 0x8c, 0x13, 0xa6, 0x1f, 0x15, 0x0d, 0x3d, 0x14, 0xc2, + 0x8f, 0xf0, 0x52, 0x83, 0x40, 0x16, 0x22, 0x3f, 0xf0, 0x8f, 0x63, 0x5f, 0x14, 0x0d, 0x51, 0x12, + 0x25, 0x59, 0xe9, 0xaa, 0x2a, 0x47, 0x9d, 0x7b, 0x60, 0xef, 0x22, 0x93, 0x6d, 0x29, 0xb9, 0xa0, + 0x75, 0xb0, 0xe2, 0x50, 0x2c, 0x1a, 0x2b, 0xd6, 0xaa, 0xbd, 0x51, 0x19, 0x7c, 0xbf, 0x63, 0x6d, + 0x6f, 0x09, 0x4f, 0x61, 0xce, 0x3a, 0x4c, 0xb7, 0x83, 0xee, 0x75, 0x34, 0x5a, 0x83, 0x0a, 0x72, + 0x7e, 0xd0, 0x13, 0xd1, 0xa2, 0xb9, 0x62, 0xac, 0xda, 0xde, 0x14, 0x72, 0xde, 0x11, 0x91, 0x53, + 0x07, 0xfb, 0x59, 0xcc, 0xa2, 0x5c, 0xa0, 0x0a, 0xc6, 0xde, 0xa2, 0xb1, 0x62, 0xac, 0x4e, 0x7b, + 0xc6, 0x5e, 0x6e, 0x01, 0xd9, 0xb5, 0x16, 0x5e, 0xc1, 0xdc, 0x66, 0x3e, 0x1d, 0x0f, 0xdf, 0x9c, + 0xa0, 0xd0, 0xa6, 0xef, 0x82, 0xd9, 0xc3, 0x4c, 0x6d, 0xa6, 0x35, 0xdf, 0xcc, 0x27, 0x9a, 0x4f, + 0xa1, 0x59, 0xd0, 0xcd, 0x1e, 0xd2, 0xdb, 0x00, 0x31, 0x93, 0x3c, 0x39, 0x90, 0x78, 0x26, 0xb5, + 0x39, 0x3b, 0x43, 0xf6, 0xf0, 0x4c, 0x3a, 0x0f, 0x61, 0xf9, 0x92, 0x78, 0x10, 0x60, 0x2a, 0x31, + 0x1c, 0xe7, 0xf8, 0xf1, 0xd0, 0xc9, 0xee, 0x91, 0xcf, 0xb1, 0x83, 0xff, 0xe2, 0xc4, 0xd9, 0x82, + 0xd9, 0x72, 0x77, 0xde, 0xeb, 0x42, 0x45, 0xaf, 0xfe, 0xef, 0x02, 0x05, 0xcb, 0x79, 0x01, 0xb5, + 0xcd, 0x52, 0x2a, 0xb6, 0x59, 0x3f, 0x96, 0x5a, 0x6b, 0x1d, 0xaa, 0xe5, 0xc0, 0x68, 0xc1, 0xa5, + 0x2b, 0x82, 0x43, 0x86, 0x37, 0xc2, 0x77, 0x76, 0xb2, 0x59, 0x0c, 0x7f, 0xef, 0xe0, 0x69, 0x27, + 0x0f, 0xd9, 0xd0, 0xaa, 0x0e, 0xdd, 0x78, 0xab, 0x9a, 0xec, 0x15, 0x2c, 0xe7, 0x39, 0xcc, 0x8f, + 0xbc, 0x0d, 0xfd, 0xf0, 0xff, 0x18, 0x7d, 0x04, 0x73, 0x5b, 0xd8, 0x97, 0x49, 0x72, 0x2c, 0x3a, + 0x7e, 0x2a, 0x50, 0x27, 0x82, 0x80, 0xd5, 0xc5, 0xf3, 0x4c, 0xcd, 0xf6, 0x54, 0xa9, 0x90, 0xbe, + 0x7f, 0xac, 0xb7, 0xae, 0x4a, 0xa7, 0x03, 0x0b, 0xbb, 0xd9, 0x8d, 0xb4, 0xd5, 0x89, 0xec, 0xa7, + 0xa1, 0x5f, 0x4c, 0x6f, 0x0d, 0x2a, 0xd9, 0xd9, 0x60, 0x9e, 0xc2, 0x99, 0x56, 0x7d, 0xd4, 0x4f, + 0xa9, 0xcd, 0x2b, 0x98, 0xce, 0x13, 0xb0, 0x77, 0x92, 0x50, 0x2b, 0x50, 0x98, 0x54, 0x47, 0x9e, + 0x19, 0xb8, 0xe1, 0x65, 0x35, 0x6d, 0x00, 0xf8, 0x52, 0xf2, 0xf8, 0xf0, 0x44, 0xa2, 0xc8, 0x8c, + 0x54, 0xbd, 0x12, 0xf2, 0xe0, 0xc2, 0x84, 0xc9, 0xa7, 0x8a, 0x38, 0x03, 0x95, 0x7d, 0xd6, 0x65, + 0xc9, 0x29, 0x23, 0x13, 0x74, 0x1a, 0x26, 0xd5, 0x75, 0x12, 0xa4, 0x15, 0xb0, 0xda, 0x41, 0x97, + 0xbc, 0x56, 0x90, 0x3a, 0x24, 0x12, 0xe5, 0x0f, 0x91, 0x91, 0x23, 0x3a, 0x07, 0x37, 0x47, 0xc3, + 0x4b, 0x3e, 0x19, 0x74, 0x19, 0x16, 0xc6, 0x27, 0x9a, 0x7c, 0x36, 0x4a, 0x1d, 0x3a, 0xc1, 0xe4, + 0x8b, 0x41, 0x67, 0xa1, 0x5a, 0x06, 0xc9, 0x57, 0x83, 0xd6, 0x80, 0x5e, 0x4d, 0x19, 0x79, 0x67, + 0x6a, 0xf5, 0x31, 0x19, 0x21, 0xef, 0x4d, 0x3a, 0x0f, 0xe4, 0xf2, 0xc2, 0xc9, 0x07, 0x53, 0xbd, + 0x74, 0x74, 0x5d, 0xe4, 0xad, 0x45, 0x17, 0x60, 0xf6, 0xca, 0x22, 0xc8, 0x2f, 0x4b, 0xfd, 0x3b, + 0x35, 0x51, 0x12, 0x6c, 0xdc, 0xff, 0x38, 0x68, 0x18, 0xdf, 0x06, 0x0d, 0xe3, 0xc7, 0xa0, 0x61, + 0x5c, 0xfc, 0x6c, 0x4c, 0xbc, 0xac, 0xe5, 0x0b, 0x91, 0x18, 0x1c, 0xb9, 0x41, 0xc2, 0xd1, 0xd5, + 0xdf, 0xd6, 0xc3, 0xa9, 0xec, 0xa3, 0xb6, 0xf6, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x86, 0x1b, 0x27, + 0xd3, 0x6d, 0x05, 0x00, 0x00, } diff --git a/core/api/p2p/kind.proto b/core/api/p2p/kind.proto index e4eea93e53..d3461f0eba 100644 --- a/core/api/p2p/kind.proto +++ b/core/api/p2p/kind.proto @@ -27,6 +27,9 @@ enum Kind { // Ping events can be use to check and measure the availability of a peer. Ping = 103; + // Seen events are sent by frontend + Seen = 104; + // // Contact events // @@ -42,6 +45,7 @@ enum Kind { ConversationInvite = 301; ConversationNewMessage = 302; + ConversationRead = 303; // Devtool events DevtoolsMapset = 401; @@ -68,6 +72,7 @@ enum Kind { message SentAttrs { repeated string ids = 1 [(gogoproto.customname) = "IDs"]; } message AckAttrs { repeated string ids = 1 [(gogoproto.customname) = "IDs"]; string err_msg = 2; } message PingAttrs { bool T = 1; } +message SeenAttrs { repeated string ids = 1 [(gogoproto.customname) = "IDs"]; } // // Contact events @@ -84,6 +89,7 @@ message ContactShareAttrs { berty.entity.Contact contact = 1; } message ConversationInviteAttrs { berty.entity.Conversation conversation = 1; } message ConversationNewMessageAttrs { berty.entity.Message message = 1; } +message ConversationReadAttrs { berty.entity.Conversation conversation = 1; } // // Devtools events diff --git a/core/node/event.go b/core/node/event.go index 55eb537a6a..90d4965de8 100644 --- a/core/node/event.go +++ b/core/node/event.go @@ -6,16 +6,16 @@ import ( "fmt" "time" + "berty.tech/core/api/node" + "berty.tech/core/api/p2p" + "berty.tech/core/entity" "berty.tech/core/pkg/errorcodes" - + "berty.tech/core/pkg/tracing" opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "go.uber.org/zap" - "berty.tech/core/api/node" - "berty.tech/core/api/p2p" - "berty.tech/core/entity" - "berty.tech/core/pkg/tracing" + "go.uber.org/zap" ) func (n *Node) AsyncWait(ctx context.Context) { @@ -85,9 +85,11 @@ func (n *Node) handleEvent(ctx context.Context, input *p2p.Event) error { p2p.Kind_ContactShareMe: n.handleContactShareMe, p2p.Kind_ConversationInvite: n.handleConversationInvite, p2p.Kind_ConversationNewMessage: n.handleConversationNewMessage, + p2p.Kind_ConversationRead: n.handleConversationRead, p2p.Kind_DevtoolsMapset: n.handleDevtoolsMapset, p2p.Kind_SenderAliasUpdate: n.handleSenderAliasUpdate, p2p.Kind_Ack: n.handleAck, + p2p.Kind_Seen: n.handleSeen, }[input.Kind] var handlingError error if !found { diff --git a/core/node/event_handlers.go b/core/node/event_handlers.go index d6363be54b..929ec0728f 100644 --- a/core/node/event_handlers.go +++ b/core/node/event_handlers.go @@ -2,6 +2,7 @@ package node import ( "context" + "fmt" "time" "berty.tech/core/pkg/errorcodes" @@ -143,6 +144,42 @@ func (n *Node) handleConversationNewMessage(ctx context.Context, input *p2p.Even return nil } +func (n *Node) handleConversationRead(ctx context.Context, input *p2p.Event) error { + _, err := input.GetConversationReadAttrs() + if err != nil { + return err + } + + sql := n.sql(ctx) + + conversation := &entity.Conversation{ID: input.ConversationID} + if err := sql.Model(conversation).Where(conversation).First(conversation).Error; err != nil { + return err + } + + attrs, err := input.GetConversationReadAttrs() + if err != nil { + return err + } + + count := 0 + // set all messagse as seen + if err := sql. + Model(&p2p.Event{}). + Where(&p2p.Event{ + ConversationID: input.ConversationID, + Kind: p2p.Kind_ConversationNewMessage, + Direction: p2p.Event_Outgoing, + }). + Count(&count). + Update("seen_at", attrs.Conversation.ReadAt). + Error; err != nil { + return err + } + logger().Debug(fmt.Sprintf("CONVERSATION_READ %+v", count)) + return nil +} + // // Devtools handlers // @@ -188,6 +225,33 @@ func (n *Node) handleSenderAliasUpdate(ctx context.Context, input *p2p.Event) er return nil } +func (n *Node) handleSeen(ctx context.Context, input *p2p.Event) error { + var seenEvents []*p2p.Event + seenCount := 0 + seenAttrs, err := input.GetSeenAttrs() + + if err != nil { + return errors.Wrap(err, "unable to unmarshal seen attrs") + } + + baseQuery := n.sql(ctx). + Model(&p2p.Event{}). + Where("id in (?)", seenAttrs.IDs) + + if err = baseQuery. + Count(&seenCount). + UpdateColumn("seen_at", input.SeenAt). + Error; err != nil { + return errors.Wrap(err, "unable to mark events as seen") + } + + for _, seenEvent := range seenEvents { + n.clientEvents <- seenEvent + } + + return nil +} + func (n *Node) handleAck(ctx context.Context, input *p2p.Event) error { var ackedEvents []*p2p.Event ackCount := 0 diff --git a/core/node/nodeapi.go b/core/node/nodeapi.go index 009287e090..9889cc33fd 100644 --- a/core/node/nodeapi.go +++ b/core/node/nodeapi.go @@ -79,15 +79,30 @@ func (n *Node) EventSeen(ctx context.Context, input *gql.Node) (*p2p.Event, erro event := &p2p.Event{} sql := n.sql(ctx) + + // get event if err := sql. Model(&p2p.Event{}). Where(&p2p.Event{ID: input.ID}). - UpdateColumn("seen_at", time.Now().UTC()). First(event). Error; err != nil { return nil, errorcodes.ErrDbUpdate.Wrap(err) } + // check if event is from another contact + if event.Direction != p2p.Event_Incoming { + return event, nil + } + + // then mark as seen + if err := sql. + Model(event). + Where(&p2p.Event{ID: event.ID}). + UpdateColumn("seen_at", time.Now().UTC()). + First(event).Error; err != nil { + return nil, errors.Wrap(err, "cannot set event as seen") + } + // mark conversation as read if event.ConversationID != "" { _, err := n.ConversationRead(ctx, &gql.Node{ID: event.ConversationID}) @@ -100,26 +115,35 @@ func (n *Node) EventSeen(ctx context.Context, input *gql.Node) (*p2p.Event, erro } func (n *Node) ConversationRead(ctx context.Context, input *gql.Node) (*entity.Conversation, error) { + var err error + // get conversation conversation := &entity.Conversation{ID: input.ID} - if err := n.sql(ctx).Model(conversation).Where(conversation).First(conversation).Error; err != nil { + if err = n.sql(ctx).Model(conversation).Where(conversation).First(conversation).Error; err != nil { return nil, err } - // check if last message has been read - event := &p2p.Event{ConversationID: conversation.ID} - count := 0 - n.sql(ctx).Model(event).Where(event).Order("created_at").Count(&count).Last(event) - if count > 0 && event.SeenAt == nil { - return conversation, nil - } - // set conversation as read conversation.ReadAt = time.Now().UTC() - if err := n.sql(ctx).Save(conversation).Error; err != nil { + if err = n.sql(ctx).Save(conversation).Error; err != nil { return nil, errors.Wrap(err, "cannot update conversation") } + // check if last message has been read + event := &p2p.Event{ConversationID: conversation.ID, Direction: p2p.Event_Incoming} + n.sql(ctx).Model(event).Where(event).Order("created_at").Last(event) + if event.SeenAt == nil { + return conversation, nil + } + + // send conversation as read + event = n.NewConversationEvent(ctx, conversation, p2p.Kind_ConversationRead) + if err = event.SetConversationReadAttrs(&p2p.ConversationReadAttrs{Conversation: conversation}); err != nil { + return nil, err + } + if err = n.EnqueueOutgoingEvent(ctx, event); err != nil { + return nil, err + } return conversation, nil } @@ -168,7 +192,6 @@ func (n *Node) ContactAcceptRequest(ctx context.Context, input *entity.Contact) // send ContactRequestAccepted event event := n.NewContactEvent(ctx, contact, p2p.Kind_ContactRequestAccepted) - if err := n.EnqueueOutgoingEvent(ctx, event); err != nil { return nil, err } diff --git a/core/node/p2pclient.go b/core/node/p2pclient.go index 157dab20ec..762d836032 100644 --- a/core/node/p2pclient.go +++ b/core/node/p2pclient.go @@ -76,3 +76,16 @@ func (n *Node) NewSenderAliasEvent(ctx context.Context, destination string, alia return event, nil } + +func (n *Node) NewSeenEvent(ctx context.Context, destination string, ids []string) (*p2p.Event, error) { + span, ctx := tracing.EnterFunc(ctx, destination, ids) + defer span.Finish() + + event := p2p.NewOutgoingEvent(ctx, n.b64pubkey, destination, p2p.Kind_Seen) + event.ID = n.NewID() + event.Kind = p2p.Kind_Seen + if err := event.SetAttrs(&p2p.SeenAttrs{IDs: ids}); err != nil { + return event, nil + } + return event, nil +}