diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h @@ -151,6 +151,8 @@ restoreFromBackupLog(const std::vector &backupLog) const = 0; virtual void addOutboundP2PMessages( const std::vector &messages) const = 0; + virtual std::vector + getOutboundP2PMessagesByID(const std::vector &ids) const = 0; virtual std::vector getAllOutboundP2PMessages() const = 0; virtual void removeOutboundP2PMessagesOlderThan( std::string lastConfirmedMessageID, diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -166,6 +166,8 @@ const std::vector &backupLog) const override; void addOutboundP2PMessages( const std::vector &messages) const override; + std::vector getOutboundP2PMessagesByID( + const std::vector &ids) const override; std::vector getAllOutboundP2PMessages() const override; void removeOutboundP2PMessagesOlderThan( std::string lastConfirmedMessageID, diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -2313,6 +2313,30 @@ } } +std::vector SQLiteQueryExecutor::getOutboundP2PMessagesByID( + const std::vector &ids) const { + std::stringstream getOutboundP2PMessageSQLStream; + getOutboundP2PMessageSQLStream << "SELECT * " + "FROM outbound_p2p_messages " + "WHERE message_id IN " + << getSQLStatementArray(ids.size()) << ";"; + std::string getOutboundP2PMessageSQL = getOutboundP2PMessageSQLStream.str(); + + SQLiteStatementWrapper preparedSQL( + SQLiteQueryExecutor::getConnection(), + getOutboundP2PMessageSQL, + "Failed to get outbound messages by ID"); + + std::vector queryResult = + getAllEntitiesByPrimaryKeys( + SQLiteQueryExecutor::getConnection(), getOutboundP2PMessageSQL, ids); + std::vector result; + for (auto &message : queryResult) { + result.emplace_back(OutboundP2PMessage(message)); + } + return result; +} + std::vector SQLiteQueryExecutor::getAllOutboundP2PMessages() const { std::string query = diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/EntityQueryHelpers.h b/native/cpp/CommonCpp/DatabaseManagers/entities/EntityQueryHelpers.h --- a/native/cpp/CommonCpp/DatabaseManagers/entities/EntityQueryHelpers.h +++ b/native/cpp/CommonCpp/DatabaseManagers/entities/EntityQueryHelpers.h @@ -75,6 +75,34 @@ return getEntityByPrimaryKeyCommon(preparedSQL); } +template +std::vector getAllEntitiesByPrimaryKeys( + sqlite3 *db, + std::string getAllEntitiesSQL, + const std::vector &keys) { + SQLiteStatementWrapper preparedSQL( + db, getAllEntitiesSQL, "Failed to fetch entities by primary key."); + + for (int i = 0; i < keys.size(); i++) { + int bindResult = bindStringToSQL(keys[i], preparedSQL, i + 1); + if (bindResult != SQLITE_OK) { + std::stringstream error_message; + error_message << "Failed to bind key to SQL statement. Details: " + << sqlite3_errstr(bindResult) << std::endl; + sqlite3_finalize(preparedSQL); + throw std::runtime_error(error_message.str()); + } + } + + std::vector allEntities; + + for (int stepResult = sqlite3_step(preparedSQL); stepResult == SQLITE_ROW; + stepResult = sqlite3_step(preparedSQL)) { + allEntities.emplace_back(T::fromSQLResult(preparedSQL, 0)); + } + return allEntities; +} + template void replaceEntity(sqlite3 *db, std::string replaceEntitySQL, const T &entity) { SQLiteStatementWrapper preparedSQL( diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -220,6 +220,8 @@ virtual jsi::Value getAllInboundP2PMessage(jsi::Runtime &rt) override; virtual jsi::Value removeInboundP2PMessages(jsi::Runtime &rt, jsi::Array ids) override; + virtual jsi::Value + getOutboundP2PMessagesByID(jsi::Runtime &rt, jsi::Array ids) override; virtual jsi::Value getAllOutboundP2PMessage(jsi::Runtime &rt) override; virtual jsi::Value markOutboundP2PMessageAsSent( jsi::Runtime &rt, diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -2420,6 +2420,63 @@ }); } +jsi::Value +CommCoreModule::getOutboundP2PMessagesByID(jsi::Runtime &rt, jsi::Array ids) { + std::vector msgIDsCPP{}; + for (auto idx = 0; idx < ids.size(rt); idx++) { + std::string msgID = ids.getValueAtIndex(rt, idx).asString(rt).utf8(rt); + msgIDsCPP.push_back(msgID); + } + + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [=, &innerRt]() { + std::string error; + std::vector messages; + + try { + messages = + DatabaseManager::getQueryExecutor().getOutboundP2PMessagesByID( + msgIDsCPP); + + } catch (std::system_error &e) { + error = e.what(); + } + auto messagesPtr = std::make_shared>( + std::move(messages)); + + this->jsInvoker_->invokeAsync( + [&innerRt, messagesPtr, error, promise]() { + if (error.size()) { + promise->reject(error); + return; + } + + jsi::Array jsiMessages = + jsi::Array(innerRt, messagesPtr->size()); + size_t writeIdx = 0; + for (const OutboundP2PMessage &msg : *messagesPtr) { + jsi::Object jsiMsg = jsi::Object(innerRt); + jsiMsg.setProperty(innerRt, "messageID", msg.message_id); + jsiMsg.setProperty(innerRt, "deviceID", msg.device_id); + jsiMsg.setProperty(innerRt, "userID", msg.user_id); + jsiMsg.setProperty(innerRt, "timestamp", msg.timestamp); + jsiMsg.setProperty(innerRt, "plaintext", msg.plaintext); + jsiMsg.setProperty(innerRt, "ciphertext", msg.ciphertext); + jsiMsg.setProperty(innerRt, "status", msg.status); + jsiMsg.setProperty( + innerRt, "supportsAutoRetry", msg.supports_auto_retry); + jsiMessages.setValueAtIndex(innerRt, writeIdx++, jsiMsg); + } + + promise->resolve(std::move(jsiMessages)); + }); + }; + GlobalDBSingleton::instance.scheduleOrRunCancellable( + job, promise, this->jsInvoker_); + }); +} + jsi::Value CommCoreModule::getAllOutboundP2PMessage(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { diff --git a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp --- a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp @@ -193,6 +193,9 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeInboundP2PMessages(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->removeInboundP2PMessages(rt, args[0].asObject(rt).asArray(rt)); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getOutboundP2PMessagesByID(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->getOutboundP2PMessagesByID(rt, args[0].asObject(rt).asArray(rt)); +} static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllOutboundP2PMessage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getAllOutboundP2PMessage(rt); } @@ -272,6 +275,7 @@ methodMap_["getSIWEBackupSecrets"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getSIWEBackupSecrets}; methodMap_["getAllInboundP2PMessage"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllInboundP2PMessage}; methodMap_["removeInboundP2PMessages"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeInboundP2PMessages}; + methodMap_["getOutboundP2PMessagesByID"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getOutboundP2PMessagesByID}; methodMap_["getAllOutboundP2PMessage"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getAllOutboundP2PMessage}; methodMap_["markOutboundP2PMessageAsSent"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_markOutboundP2PMessageAsSent}; methodMap_["removeOutboundP2PMessagesOlderThan"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeOutboundP2PMessagesOlderThan}; diff --git a/native/cpp/CommonCpp/_generated/commJSI.h b/native/cpp/CommonCpp/_generated/commJSI.h --- a/native/cpp/CommonCpp/_generated/commJSI.h +++ b/native/cpp/CommonCpp/_generated/commJSI.h @@ -78,6 +78,7 @@ virtual jsi::Value getSIWEBackupSecrets(jsi::Runtime &rt) = 0; virtual jsi::Value getAllInboundP2PMessage(jsi::Runtime &rt) = 0; virtual jsi::Value removeInboundP2PMessages(jsi::Runtime &rt, jsi::Array ids) = 0; + virtual jsi::Value getOutboundP2PMessagesByID(jsi::Runtime &rt, jsi::Array ids) = 0; virtual jsi::Value getAllOutboundP2PMessage(jsi::Runtime &rt) = 0; virtual jsi::Value markOutboundP2PMessageAsSent(jsi::Runtime &rt, jsi::String messageID, jsi::String deviceID) = 0; virtual jsi::Value removeOutboundP2PMessagesOlderThan(jsi::Runtime &rt, jsi::String messageID, jsi::String deviceID) = 0; @@ -569,6 +570,14 @@ return bridging::callFromJs( rt, &T::removeInboundP2PMessages, jsInvoker_, instance_, std::move(ids)); } + jsi::Value getOutboundP2PMessagesByID(jsi::Runtime &rt, jsi::Array ids) override { + static_assert( + bridging::getParameterCount(&T::getOutboundP2PMessagesByID) == 2, + "Expected getOutboundP2PMessagesByID(...) to have 2 parameters"); + + return bridging::callFromJs( + rt, &T::getOutboundP2PMessagesByID, jsInvoker_, instance_, std::move(ids)); + } jsi::Value getAllOutboundP2PMessage(jsi::Runtime &rt) override { static_assert( bridging::getParameterCount(&T::getAllOutboundP2PMessage) == 1, diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -154,6 +154,9 @@ +getSIWEBackupSecrets: () => Promise; +getAllInboundP2PMessage: () => Promise; +removeInboundP2PMessages: (ids: $ReadOnlyArray) => Promise; + +getOutboundP2PMessagesByID: ( + ids: $ReadOnlyArray, + ) => Promise<$ReadOnlyArray>; +getAllOutboundP2PMessage: () => Promise; +markOutboundP2PMessageAsSent: ( messageID: string, diff --git a/web/cpp/SQLiteQueryExecutorBindings.cpp b/web/cpp/SQLiteQueryExecutorBindings.cpp --- a/web/cpp/SQLiteQueryExecutorBindings.cpp +++ b/web/cpp/SQLiteQueryExecutorBindings.cpp @@ -295,6 +295,9 @@ .function( "removeAllOutboundP2PMessages", &SQLiteQueryExecutor::removeAllOutboundP2PMessages) + .function( + "getOutboundP2PMessagesByID", + &SQLiteQueryExecutor::getOutboundP2PMessagesByID) .function( "getAllOutboundP2PMessages", &SQLiteQueryExecutor::getAllOutboundP2PMessages) diff --git a/web/shared-worker/_generated/comm-query-executor.js b/web/shared-worker/_generated/comm-query-executor.js --- a/web/shared-worker/_generated/comm-query-executor.js +++ b/web/shared-worker/_generated/comm-query-executor.js @@ -116,7 +116,7 @@ V.prototype.xd=function(a){this.Tc&&(a=this.Tc(a));return a};V.prototype.Hc=function(a){this.pb&&this.pb(a)};V.prototype.argPackAdvance=8;V.prototype.readValueFromPointer=lb;V.prototype.deleteObject=function(a){if(null!==a)a["delete"]()}; V.prototype.fromWireType=function(a){function b(){return this.Vb?Jb(this.Ta.Eb,{Xa:this.Md,Sa:c,fb:this,ab:a}):Jb(this.Ta.Eb,{Xa:this,Sa:a})}var c=this.xd(a);if(!c)return this.Hc(a),null;var d=Ib(this.Ta,c);if(void 0!==d){if(0===d.Na.count.value)return d.Na.Sa=c,d.Na.ab=a,d.clone();d=d.clone();this.Hc(a);return d}d=this.Ta.vd(c);d=Db[d];if(!d)return b.call(this);d=this.Tb?d.jd:d.pointerType;var f=Cb(c,this.Ta,d.Ta);return null===f?b.call(this):this.Vb?Jb(d.Ta.Eb,{Xa:d,Sa:f,fb:this,ab:a}):Jb(d.Ta.Eb, {Xa:d,Sa:f})};Wb=e.UnboundTypeError=rb("UnboundTypeError");e.count_emval_handles=function(){for(var a=0,b=5;bf?-28:N.Fc(d,f).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return f=hb(),d.flags|=f,0;case 5:return f=hb(),D[f+0>>1]=2,0;case 6:case 7:return 0;case 16:case 8:return-28;case 9:return E[Kc()>>2]=28,-1;default:return-28}}catch(g){if("undefined"== typeof N||!(g instanceof N.Ma))throw g;return-g.Ra}},X:function(a,b){try{var c=Q(a);return fb(N.stat,c.path,b)}catch(d){if("undefined"==typeof N||!(d instanceof N.Ma))throw d;return-d.Ra}},E:function(a,b,c){try{b=c+2097152>>>0<4194305-!!b?(b>>>0)+4294967296*c:NaN;if(isNaN(b))return-61;N.ud(a,b);return 0}catch(d){if("undefined"==typeof N||!(d instanceof N.Ma))throw d;return-d.Ra}},S:function(a,b){try{if(0===b)return-28;var c=N.cwd(),d=ta(c)+1;if(b>>0,(G=h,1<=+Math.abs(G)?0>>0:~~+Math.ceil((G-+(~~G>>>0))/4294967296)>>> @@ -135,7 +135,7 @@ h=>h;if(0===d){var k=32-8*c;g=h=>h<>>k}c=b.includes("unsigned")?function(h,n){return n>>>0}:function(h,n){return n};R(a,{name:b,fromWireType:g,toWireType:c,argPackAdvance:8,readValueFromPointer:gc(b,f,0!==d),ib:null})},g:function(a,b,c){function d(g){g>>=2;var k=F;return new f(ua,k[g+1],k[g])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=S(c);R(a,{name:c,fromWireType:d,argPackAdvance:8,readValueFromPointer:d},{Ad:!0})},y:function(a,b){b= S(b);var c="std::string"===b;R(a,{name:b,fromWireType:function(d){var f=F[d>>2],g=d+4;if(c)for(var k=g,h=0;h<=f;++h){var n=g+h;if(h==f||0==z[n]){k=y(k,n-k);if(void 0===q)var q=k;else q+=String.fromCharCode(0),q+=k;k=n+1}}else{q=Array(f);for(h=0;h>2]=k;if(c&&g)A(f,z,n,k+1);else if(g)for(g=0;gva;var h=1}else 4===b&&(d=lc,f=mc,g=nc,k=()=>F,h=2);R(a,{name:c,fromWireType:function(n){for(var q= -F[n>>2],p=k(),t,x=n+4,l=0;l<=q;++l){var u=n+4+l*b;if(l==q||0==p[u>>h])x=d(x,u-x),void 0===t?t=x:(t+=String.fromCharCode(0),t+=x),x=u+b}X(n);return t},toWireType:function(n,q){"string"!=typeof q&&T("Cannot pass non-string to C++ string type "+c);var p=g(q),t=wc(4+p+b);F[t>>2]=p>>h;f(q,t+4,p+b);null!==n&&n.push(X,t);return t},argPackAdvance:8,readValueFromPointer:lb,ib:function(n){X(n)}})},f:function(a,b,c,d,f,g){jb[a]={name:S(b),rc:W(c,d),pb:W(f,g),Lc:[]}},c:function(a,b,c,d,f,g,k,h,n,q){jb[a].Lc.push({rd:S(b), +F[n>>2],p=k(),t,x=n+4,l=0;l<=q;++l){var u=n+4+l*b;if(l==q||0==p[u>>h])x=d(x,u-x),void 0===t?t=x:(t+=String.fromCharCode(0),t+=x),x=u+b}X(n);return t},toWireType:function(n,q){"string"!=typeof q&&T("Cannot pass non-string to C++ string type "+c);var p=g(q),t=wc(4+p+b);F[t>>2]=p>>h;f(q,t+4,p+b);null!==n&&n.push(X,t);return t},argPackAdvance:8,readValueFromPointer:lb,ib:function(n){X(n)}})},f:function(a,b,c,d,f,g){jb[a]={name:S(b),rc:W(c,d),pb:W(f,g),Lc:[]}},d:function(a,b,c,d,f,g,k,h,n,q){jb[a].Lc.push({rd:S(b), zd:c,Sb:W(d,f),yd:g,Sd:k,Rd:W(h,n),Td:q})},ga:function(a,b){b=S(b);R(a,{Dd:!0,name:b,argPackAdvance:0,fromWireType:function(){},toWireType:function(){}})},o:function(){return Date.now()},aa:function(){return!0},k:function(a,b,c){a=ec(a);b=oc(b,"emval::as");var d=[],f=Sb(d);F[c>>2]=f;return b.toWireType(d,a)},A:function(a,b,c,d){a=rc[a];b=ec(b);c=qc(c);a(b,c,null,d)},na:dc,oa:function(a,b){var c=tc(a,b),d=c[0];b=d.name+"_$"+c.slice(1).map(function(p){return p.name}).join("_")+"$";var f=uc[b];if(void 0!== f)return f;f=["retType"];for(var g=[d],k="",h=0;h>2]=a.getSeconds();E[b+4>>2]=a.getMinutes();E[b+8>>2]=a.getHours();E[b+12>>2]=a.getDate();E[b+16>>2]=a.getMonth();E[b+20>>2]=a.getFullYear()- diff --git a/web/shared-worker/_generated/comm_query_executor.wasm b/web/shared-worker/_generated/comm_query_executor.wasm index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ msg.messageID === TEST_MSG_4.messageID)?.status, ).toBe('sent'); }); + + it('should return message by ID', () => { + expect(queryExecutor?.getOutboundP2PMessagesByID(['id-4'])).toEqual([ + TEST_MSG_4, + ]); + }); + + it('should return message by IDs', () => { + expect(queryExecutor?.getOutboundP2PMessagesByID(['id-4', 'id-2'])).toEqual( + [TEST_MSG_2, TEST_MSG_4], + ); + }); + + it(`should return undefined when a message with ID doesn't exist`, () => { + expect(queryExecutor?.getOutboundP2PMessagesByID(['id-5'])).toEqual([]); + }); }); diff --git a/web/shared-worker/types/sqlite-query-executor.js b/web/shared-worker/types/sqlite-query-executor.js --- a/web/shared-worker/types/sqlite-query-executor.js +++ b/web/shared-worker/types/sqlite-query-executor.js @@ -180,6 +180,9 @@ deviceID: string, ): void; removeAllOutboundP2PMessages(deviceID: string): void; + getOutboundP2PMessagesByID( + ids: $ReadOnlyArray, + ): $ReadOnlyArray; getAllOutboundP2PMessages(): $ReadOnlyArray; setCiphertextForOutboundP2PMessage( messageID: string,