1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "distributed_extension_context_js.h"
17
18 #include "dtbschedmgr_log.h"
19 #include "js_data_struct_converter.h"
20 #include "js_error_utils.h"
21 #include "js_extension_context.h"
22 #include "js_runtime.h"
23 #include "js_runtime_utils.h"
24 #include "js_utils.h"
25 #include "napi/native_api.h"
26 #include "napi_common_start_options.h"
27 #include "napi_common_util.h"
28 #include "napi_common_want.h"
29 #include "napi_remote_object.h"
30 #include "start_options.h"
31
32 namespace OHOS {
33 namespace DistributedSchedule {
34 const std::string TAG = "DistributedExtensionContextJS";
35 using namespace AbilityRuntime;
36
37 constexpr int32_t INDEX_ZERO = 0;
38 constexpr int32_t INDEX_ONE = 1;
39 constexpr int32_t ERROR_CODE_ONE = 1;
40 constexpr int32_t ERROR_CODE_TWO = 2;
41 constexpr size_t ARGC_ONE = 1;
42 constexpr size_t ARGC_TWO = 2;
43
44 class DistributedExtensionContextJS final {
45 public:
DistributedExtensionContextJS(const std::shared_ptr<DistributedExtensionContext> & ct)46 explicit DistributedExtensionContextJS(const std::shared_ptr<DistributedExtensionContext>& ct) : context(ct) {}
47 ~DistributedExtensionContextJS() = default;
48
49 static void Finalizer(napi_env env, void* data, void* hint);
50
ConnectAbility(napi_env env,napi_callback_info info)51 static napi_value ConnectAbility(napi_env env, napi_callback_info info)
52 {
53 GET_CB_INFO_AND_CALL(env, info, DistributedExtensionContextJS, OnConnectAbility);
54 }
55
DisconnectAbility(napi_env env,napi_callback_info info)56 static napi_value DisconnectAbility(napi_env env, napi_callback_info info)
57 {
58 GET_CB_INFO_AND_CALL(env, info, DistributedExtensionContextJS, OnDisconnectAbility);
59 }
60
61 private:
62 std::weak_ptr<DistributedExtensionContext> context;
63
OnConnectAbility(napi_env env,size_t argc,napi_value * argv)64 napi_value OnConnectAbility(napi_env env, size_t argc, napi_value *argv)
65 {
66 HILOGI("OnConnectAbility start.");
67 if (argc != ARGC_TWO) {
68 HILOGE("test failed: not enough params!");
69 return CreateJsUndefined(env);
70 }
71 AAFwk::Want want;
72
73 if (!OHOS::AppExecFwk::UnwrapWant(env, argv[INDEX_ZERO], want)) {
74 HILOGE("parse want failed");
75 return CreateJsUndefined(env);
76 }
77 HILOGI("%{public}s bundleName: %{public}s abilityName: %{public}s", __func__, want.GetBundle().c_str(),
78 want.GetElement().GetAbilityName().c_str());
79 sptr<DistributedExtensionContextJSConnection> connection = new DistributedExtensionContextJSConnection(env);
80 if (connection == nullptr) {
81 return nullptr;
82 }
83 connection->SetJsConnectionObject(argv[1]);
84 int64_t connectId = serialNumber_;
85 ConnectionKey key;
86 key.id = serialNumber_;
87 key.want = want;
88 {
89 std::lock_guard<std::mutex> lock(g_connectMapMtx);
90 connects_.emplace(key, connection);
91 }
92 if (serialNumber_ < INT64_MAX) {
93 serialNumber_++;
94 } else {
95 serialNumber_ = 0;
96 }
97
98 return StartConnectAsyncTask(env, connectId, want, connection);
99 }
100
StartConnectAsyncTask(napi_env env,int64_t connectId,const AAFwk::Want & want,const sptr<DistributedExtensionContextJSConnection> & connection)101 napi_value StartConnectAsyncTask(napi_env env, int64_t connectId, const AAFwk::Want &want,
102 const sptr<DistributedExtensionContextJSConnection> &connection)
103 {
104 napi_value result = nullptr;
105 napi_value lastParam = nullptr;
106 napi_value connectResult = nullptr;
107 std::unique_ptr<NapiAsyncTask> napiAsyncTask = CreateEmptyAsyncTask(env, lastParam, &result);
108 auto asyncTask = [weak = context, want, connection, connectId, env, task = napiAsyncTask.get()]() {
109 HILOGI("OnConnectAbility start.");
110 auto context = weak.lock();
111 if (context == nullptr) {
112 HILOGW("context is released.");
113 task->Reject(env, CreateJsError(env, ERROR_CODE_ONE, "Context is released"));
114 delete task;
115 return;
116 }
117 HILOGI("context->ConnectAbility connection: %{public}d.", static_cast<int32_t>(connectId));
118 if (!context->ConnectAbility(want, connection)) {
119 connection->CallJsFailed(ERROR_CODE_ONE);
120 }
121 task->Resolve(env, CreateJsUndefined(env));
122 delete task;
123 };
124 if (napi_send_event(env, asyncTask, napi_eprio_high) != napi_status::napi_ok) {
125 napiAsyncTask->Reject(env, CreateJsError(env, ERROR_CODE_ONE, "send event failed"));
126 } else {
127 napiAsyncTask.release();
128 }
129 napi_create_int64(env, connectId, &connectResult);
130 return connectResult;
131 }
132
OnDisconnectAbility(napi_env env,size_t argc,napi_value * argv)133 napi_value OnDisconnectAbility(napi_env env, size_t argc, napi_value *argv)
134 {
135 HILOGI("OnDisconnectAbility is called.");
136 if (!(argc == ARGC_ONE || argc == ARGC_TWO)) {
137 HILOGE("test failed: not enough params!");
138 return CreateJsUndefined(env);
139 }
140 AAFwk::Want want;
141 int64_t connectId = -1;
142 sptr<DistributedExtensionContextJSConnection> connection = nullptr;
143 napi_get_value_int64(env, argv[INDEX_ZERO], &connectId);
144 HILOGI("OnDisconnectAbility connection: %{public}d.", static_cast<int32_t>(connectId));
145 {
146 std::lock_guard<std::mutex> lock(g_connectMapMtx);
147 auto item = std::find_if(connects_.begin(), connects_.end(),
148 [connectId](const std::map<ConnectionKey,
149 sptr<DistributedExtensionContextJSConnection>>::value_type &obj) {
150 return connectId == obj.first.id;
151 });
152 if (item != connects_.end()) {
153 want = item->first.want;
154 connection = item->second;
155 }
156 }
157 napi_value lastParam = argc == ARGC_ONE ? nullptr : argv[INDEX_ONE];
158 return StartDisconnectAsyncTask(env, want, connection, lastParam);
159 }
160
StartDisconnectAsyncTask(napi_env env,const AAFwk::Want & want,const sptr<DistributedExtensionContextJSConnection> & connection,napi_value lastParam)161 napi_value StartDisconnectAsyncTask(napi_env env, const AAFwk::Want &want,
162 const sptr<DistributedExtensionContextJSConnection> &connection, napi_value lastParam)
163 {
164 napi_value result = nullptr;
165 std::unique_ptr<NapiAsyncTask> napiAsyncTask = CreateEmptyAsyncTask(env, lastParam, &result);
166 auto asyncTask = [weak = context, want, connection, env, task = napiAsyncTask.get()]() {
167 HILOGI("OnDisconnectAbility start.");
168 auto context = weak.lock();
169 if (context == nullptr) {
170 HILOGW("context is released.");
171 task->Reject(env, CreateJsError(env, ERROR_CODE_ONE, "Context is released"));
172 delete task;
173 return;
174 }
175 if (connection == nullptr) {
176 HILOGW("connection is nullptr.");
177 task->Reject(env, CreateJsError(env, ERROR_CODE_TWO, "not found connection"));
178 delete task;
179 return;
180 }
181 HILOGI("context->DisconnectAbility.");
182 auto errcode = context->DisconnectAbility(want, connection);
183 errcode == 0 ? task->Resolve(env, CreateJsUndefined(env))
184 : task->Reject(env, CreateJsError(env, errcode, "Disconnect Ability failed."));
185 delete task;
186 };
187 if (napi_send_event(env, asyncTask, napi_eprio_high) != napi_status::napi_ok) {
188 napiAsyncTask->Reject(env, CreateJsError(env, ERROR_CODE_ONE, "send event failed"));
189 } else {
190 napiAsyncTask.release();
191 }
192 return result;
193 }
194 };
195
CreateDistributedExtensionContextJS(napi_env env,std::shared_ptr<DistributedExtensionContext> context)196 napi_value CreateDistributedExtensionContextJS(napi_env env, std::shared_ptr<DistributedExtensionContext> context)
197 {
198 if (context == nullptr) {
199 HILOGE("Failed to CreateDistributedExtensionContextJS, context is nullptr.");
200 return nullptr;
201 }
202 std::shared_ptr<OHOS::AppExecFwk::AbilityInfo> abilityInfo = context->GetAbilityInfo();
203 napi_value object = CreateJsExtensionContext(env, context, abilityInfo);
204 if (object == nullptr) {
205 HILOGE("Failed to CreateJsServiceExtensionContext, context is nullptr.");
206 return nullptr;
207 }
208 std::unique_ptr<DistributedExtensionContextJS> jsContext =
209 std::make_unique<DistributedExtensionContextJS>(context);
210 napi_wrap(env, object, jsContext.release(), DistributedExtensionContextJS::Finalizer, nullptr, nullptr);
211
212 const char *moduleName = "DistributedExtensionContextJS";
213 BindNativeFunction(env, object, "connectAbility", moduleName, DistributedExtensionContextJS::ConnectAbility);
214 BindNativeFunction(env, object, "disconnectAbility", moduleName,
215 DistributedExtensionContextJS::DisconnectAbility);
216 return object;
217 }
218
Finalizer(napi_env env,void * data,void * hint)219 void DistributedExtensionContextJS::Finalizer(napi_env env, void* data, void* hint)
220 {
221 HILOGI("Finalizer Called.");
222 std::unique_ptr<DistributedExtensionContextJS>(static_cast<DistributedExtensionContextJS*>(data));
223 }
224
CreateJsMetadata(napi_env env,const AppExecFwk::Metadata & info)225 napi_value CreateJsMetadata(napi_env env, const AppExecFwk::Metadata &info)
226 {
227 HILOGI("CreateJsMetadata start.");
228
229 napi_value objValue = nullptr;
230 napi_create_object(env, &objValue);
231
232 napi_set_named_property(env, objValue, "name", CreateJsValue(env, info.name));
233 napi_set_named_property(env, objValue, "value", CreateJsValue(env, info.value));
234 napi_set_named_property(env, objValue, "resource", CreateJsValue(env, info.resource));
235 return objValue;
236 }
237
CreateJsMetadataArray(napi_env env,const std::vector<AppExecFwk::Metadata> & info)238 napi_value CreateJsMetadataArray(napi_env env, const std::vector<AppExecFwk::Metadata> &info)
239 {
240 HILOGI("CreateJsMetadataArray start.");
241 napi_value arrayValue = nullptr;
242 napi_create_array_with_length(env, info.size(), &arrayValue);
243 uint32_t index = 0;
244 for (const auto &item : info) {
245 napi_set_element(env, arrayValue, index++, CreateJsMetadata(env, item));
246 }
247 return arrayValue;
248 }
249
CreateJsExtensionAbilityInfo(napi_env env,const AppExecFwk::ExtensionAbilityInfo & info)250 napi_value CreateJsExtensionAbilityInfo(napi_env env, const AppExecFwk::ExtensionAbilityInfo &info)
251 {
252 HILOGI("CreateJsExtensionAbilityInfo start.");
253 napi_value objValue = nullptr;
254 napi_create_object(env, &objValue);
255
256 napi_set_named_property(env, objValue, "bundleName", CreateJsValue(env, info.bundleName));
257 napi_set_named_property(env, objValue, "moduleName", CreateJsValue(env, info.moduleName));
258 napi_set_named_property(env, objValue, "name", CreateJsValue(env, info.name));
259 napi_set_named_property(env, objValue, "labelId", CreateJsValue(env, info.labelId));
260 napi_set_named_property(env, objValue, "descriptionId", CreateJsValue(env, info.descriptionId));
261 napi_set_named_property(env, objValue, "iconId", CreateJsValue(env, info.iconId));
262 napi_set_named_property(env, objValue, "isVisible", CreateJsValue(env, info.visible));
263 napi_set_named_property(env, objValue, "extensionAbilityType", CreateJsValue(env, info.type));
264
265 napi_value permissionArray = nullptr;
266 napi_create_array_with_length(env, info.permissions.size(), &permissionArray);
267
268 if (permissionArray != nullptr) {
269 int32_t index = 0;
270 for (auto permission : info.permissions) {
271 napi_set_element(env, permissionArray, index++, CreateJsValue(env, permission));
272 }
273 }
274 napi_set_named_property(env, objValue, "permissions", permissionArray);
275 napi_set_named_property(env, objValue, "applicationInfo", CreateJsApplicationInfo(env, info.applicationInfo));
276 napi_set_named_property(env, objValue, "metadata", CreateJsMetadataArray(env, info.metadata));
277 napi_set_named_property(env, objValue, "enabled", CreateJsValue(env, info.enabled));
278 napi_set_named_property(env, objValue, "readPermission", CreateJsValue(env, info.readPermission));
279 napi_set_named_property(env, objValue, "writePermission", CreateJsValue(env, info.writePermission));
280 return objValue;
281 }
282
DistributedExtensionContextJSConnection(napi_env env)283 DistributedExtensionContextJSConnection::DistributedExtensionContextJSConnection(napi_env env) : env_(env),
284 handler_(std::make_shared<AppExecFwk::EventHandler>(AppExecFwk::EventRunner::GetMainEventRunner()))
285 {
286 }
287
~DistributedExtensionContextJSConnection()288 DistributedExtensionContextJSConnection::~DistributedExtensionContextJSConnection()
289 {
290 ReleaseConnection();
291 }
292
OnAbilityConnectDone(const AppExecFwk::ElementName & element,const sptr<IRemoteObject> & remoteObject,int32_t resultCode)293 void DistributedExtensionContextJSConnection::OnAbilityConnectDone(const AppExecFwk::ElementName &element,
294 const sptr<IRemoteObject> &remoteObject, int32_t resultCode)
295 {
296 HILOGI("OnAbilityConnectDone start, resultCode: %{public}d.", resultCode);
297 if (jsConnectionObject_ == nullptr) {
298 HILOGE("jsConnectionObject_ is nullptr!");
299 ReleaseConnection();
300 return;
301 }
302 if (handler_ == nullptr) {
303 HILOGI("handler_ is nullptr.");
304 return;
305 }
306 wptr<DistributedExtensionContextJSConnection> connection = this;
307 auto task = [connection, element, remoteObject, resultCode]() {
308 sptr<DistributedExtensionContextJSConnection> connectionSptr = connection.promote();
309 if (connectionSptr == nullptr) {
310 HILOGE("connectionSptr is nullptr.");
311 return;
312 }
313 connectionSptr->HandleOnAbilityConnectDone(element, remoteObject, resultCode);
314 };
315 handler_->PostTask(task, "OnAbilityConnectDone", 0, AppExecFwk::EventQueue::Priority::VIP);
316 }
317
HandleOnAbilityConnectDone(const AppExecFwk::ElementName & element,const sptr<IRemoteObject> & remoteObject,int32_t resultCode)318 void DistributedExtensionContextJSConnection::HandleOnAbilityConnectDone(const AppExecFwk::ElementName &element,
319 const sptr<IRemoteObject> &remoteObject, int32_t resultCode)
320 {
321 HILOGI("HandleOnAbilityConnectDone start, resultCode:%{public}d.", resultCode);
322 napi_value napiElementName = OHOS::AppExecFwk::WrapElementName(env_, element);
323
324 HILOGI("OnAbilityConnectDone start NAPI_ohos_rpc_CreateJsRemoteObject.");
325 napi_value napiRemoteObject = NAPI_ohos_rpc_CreateJsRemoteObject(env_, remoteObject);
326 napi_value argv[] = { napiElementName, napiRemoteObject };
327
328 if (jsConnectionObject_ == nullptr) {
329 HILOGE("jsConnectionObject_ is nullptr!");
330 return;
331 }
332
333 napi_value obj = nullptr;
334 if (napi_get_reference_value(env_, jsConnectionObject_, &obj) != napi_ok) {
335 HILOGE("failed to get jsConnectionObject_!");
336 return;
337 }
338 if (obj == nullptr) {
339 HILOGE("failed to get object!");
340 return;
341 }
342 napi_value methodOnConnect = nullptr;
343 napi_get_named_property(env_, obj, "onConnect", &methodOnConnect);
344 if (methodOnConnect == nullptr) {
345 HILOGE("failed to get onConnect from object!");
346 return;
347 }
348 HILOGI("DistributedExtensionContextJSConnection::CallFunction onConnect, success.");
349 napi_value callResult = nullptr;
350 napi_call_function(env_, obj, methodOnConnect, ARGC_TWO, argv, &callResult);
351 HILOGI("OnAbilityConnectDone end.");
352 }
353
OnAbilityDisconnectDone(const AppExecFwk::ElementName & element,int32_t resultCode)354 void DistributedExtensionContextJSConnection::OnAbilityDisconnectDone(const AppExecFwk::ElementName &element,
355 int32_t resultCode)
356 {
357 HILOGI("OnAbilityDisconnectDone start, resultCode: %{public}d.", resultCode);
358 if (handler_ == nullptr) {
359 HILOGI("handler_ is nullptr.");
360 return;
361 }
362 wptr<DistributedExtensionContextJSConnection> connection = this;
363 auto task = [connection, element, resultCode]() {
364 sptr<DistributedExtensionContextJSConnection> connectionSptr = connection.promote();
365 if (!connectionSptr) {
366 HILOGE("connectionSptr is nullptr.");
367 return;
368 }
369 connectionSptr->HandleOnAbilityDisconnectDone(element, resultCode);
370 };
371 handler_->PostTask(task, "OnAbilityDisconnectDone", 0, AppExecFwk::EventQueue::Priority::VIP);
372 }
373
HandleOnAbilityDisconnectDone(const AppExecFwk::ElementName & element,int32_t resultCode)374 void DistributedExtensionContextJSConnection::HandleOnAbilityDisconnectDone(const AppExecFwk::ElementName &element,
375 int32_t resultCode)
376 {
377 HILOGI("HandleOnAbilityDisconnectDone start, resultCode:%{public}d.", resultCode);
378 napi_value napiElementName = OHOS::AppExecFwk::WrapElementName(env_, element);
379 napi_value argv[] = { napiElementName };
380 if (jsConnectionObject_ == nullptr) {
381 HILOGE("jsConnectionObject_ is nullptr!");
382 return;
383 }
384 napi_value obj = nullptr;
385 if (napi_get_reference_value(env_, jsConnectionObject_, &obj) != napi_ok) {
386 HILOGE("failed to get jsConnectionObject_!");
387 return;
388 }
389 if (obj == nullptr) {
390 HILOGE("failed to get object!");
391 return;
392 }
393 napi_value method = nullptr;
394 napi_get_named_property(env_, obj, "onDisconnect", &method);
395 if (method == nullptr) {
396 HILOGE("failed to get onDisconnect from object!");
397 return;
398 }
399 std::string bundleName = element.GetBundleName();
400 std::string abilityName = element.GetAbilityName();
401 {
402 std::lock_guard<std::mutex> lock(g_connectMapMtx);
403 HILOGI("OnAbilityDisconnectDone connects_.size: %{public}zu.", connects_.size());
404 auto item = std::find_if(connects_.begin(), connects_.end(),
405 [bundleName, abilityName](
406 const std::map<ConnectionKey, sptr<DistributedExtensionContextJSConnection>>::value_type &obj) {
407 return (bundleName == obj.first.want.GetBundle()) &&
408 (abilityName == obj.first.want.GetElement().GetAbilityName());
409 });
410 if (item != connects_.end()) {
411 if (item->second != nullptr) {
412 item->second->ReleaseConnection();
413 }
414 connects_.erase(item);
415 HILOGI("OnAbilityDisconnectDone erase connects_.size: %{public}zu.", connects_.size());
416 }
417 }
418 HILOGI("OnAbilityDisconnectDone CallFunction success.");
419 napi_value callResult = nullptr;
420 napi_call_function(env_, obj, method, ARGC_ONE, argv, &callResult);
421 }
422
SetJsConnectionObject(napi_value jsConnectionObject)423 void DistributedExtensionContextJSConnection::SetJsConnectionObject(napi_value jsConnectionObject)
424 {
425 napi_create_reference(env_, jsConnectionObject, 1, &jsConnectionObject_);
426 }
427
CallJsFailed(int32_t errorCode)428 void DistributedExtensionContextJSConnection::CallJsFailed(int32_t errorCode)
429 {
430 HILOGI("CallJsFailed start");
431 if (jsConnectionObject_ == nullptr) {
432 HILOGE("jsConnectionObject_ is nullptr!");
433 return;
434 }
435 napi_value obj = nullptr;
436 if (napi_get_reference_value(env_, jsConnectionObject_, &obj) != napi_ok) {
437 HILOGE("failed to get jsConnectionObject_!");
438 return;
439 }
440 if (obj == nullptr) {
441 HILOGE("failed to get object.");
442 return;
443 }
444
445 napi_value method = nullptr;
446 napi_get_named_property(env_, obj, "onFailed", &method);
447 if (method == nullptr) {
448 HILOGE("failed to get onFailed from object!");
449 return;
450 }
451 napi_value result = nullptr;
452 napi_create_int32(env_, errorCode, &result);
453 napi_value argv[] = { result };
454 HILOGI("CallJsFailed CallFunction success.");
455 napi_value callResult = nullptr;
456 napi_call_function(env_, obj, method, ARGC_ONE, argv, &callResult);
457 HILOGI("CallJsFailed end.");
458 }
459
ReleaseConnection()460 void DistributedExtensionContextJSConnection::ReleaseConnection()
461 {
462 HILOGI("ReleaseConnection");
463 if (jsConnectionObject_ != nullptr) {
464 napi_delete_reference(env_, jsConnectionObject_);
465 env_ = nullptr;
466 jsConnectionObject_ = nullptr;
467 }
468 }
469 }
470 }
471