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 "js_app_service_extension_context.h"
17 #include "js_service_extension_context.h"
18 #include <chrono>
19 #include <cstdint>
20
21 #include "ability_manager_client.h"
22 #include "ability_runtime/js_caller_complex.h"
23 #include "hilog_tag_wrapper.h"
24 #include "js_extension_context.h"
25 #include "js_error_utils.h"
26 #include "js_data_struct_converter.h"
27 #include "js_runtime.h"
28 #include "js_runtime_utils.h"
29 #include "napi/native_api.h"
30 #include "napi_common_ability.h"
31 #include "napi_common_want.h"
32 #include "napi_common_util.h"
33 #include "napi_remote_object.h"
34 #include "napi_common_start_options.h"
35 #include "open_link_options.h"
36 #include "open_link/napi_common_open_link_options.h"
37 #include "start_options.h"
38 #include "hitrace_meter.h"
39 #include "uri.h"
40
41 namespace OHOS {
42 namespace AbilityRuntime {
43 namespace {
44 constexpr int32_t INDEX_ZERO = 0;
45 constexpr int32_t INDEX_ONE = 1;
46 constexpr int32_t ERR_INVALID_VALUE = -1;
47 constexpr size_t ARGC_ZERO = 0;
48 constexpr size_t ARGC_ONE = 1;
49 constexpr size_t ARGC_TWO = 2;
50
51 static std::mutex g_connectsMutex;
52 static std::map<ConnectionKey, sptr<JSAppServiceExtensionConnection>, key_compare> g_connects;
53 static int64_t g_serialNumber = 0;
54
RemoveConnection(int64_t connectId)55 void RemoveConnection(int64_t connectId)
56 {
57 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "enter");
58 std::lock_guard guard(g_connectsMutex);
59 auto item = std::find_if(g_connects.begin(), g_connects.end(),
60 [&connectId](const auto &obj) {
61 return connectId == obj.first.id;
62 });
63 if (item != g_connects.end()) {
64 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "remove conn ability exist");
65 if (item->second) {
66 item->second->RemoveConnectionObject();
67 }
68 g_connects.erase(item);
69 } else {
70 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "remove conn ability not exist");
71 }
72 }
73
74 class JsAppServiceExtensionContext final {
75 public:
JsAppServiceExtensionContext(const std::shared_ptr<AppServiceExtensionContext> & context)76 explicit JsAppServiceExtensionContext(
77 const std::shared_ptr<AppServiceExtensionContext>& context) : context_(context) {}
78 ~JsAppServiceExtensionContext() = default;
79
Finalizer(napi_env env,void * data,void * hint)80 static void Finalizer(napi_env env, void* data, void* hint)
81 {
82 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "called");
83 std::unique_ptr<JsAppServiceExtensionContext>(static_cast<JsAppServiceExtensionContext*>(data));
84 }
85
ConnectAbility(napi_env env,napi_callback_info info)86 static napi_value ConnectAbility(napi_env env, napi_callback_info info)
87 {
88 GET_NAPI_INFO_AND_CALL(env, info, JsAppServiceExtensionContext, OnConnectAbility);
89 }
90
DisconnectAbility(napi_env env,napi_callback_info info)91 static napi_value DisconnectAbility(napi_env env, napi_callback_info info)
92 {
93 GET_NAPI_INFO_AND_CALL(env, info, JsAppServiceExtensionContext, OnDisconnectAbility);
94 }
95
StartAbility(napi_env env,napi_callback_info info)96 static napi_value StartAbility(napi_env env, napi_callback_info info)
97 {
98 GET_NAPI_INFO_AND_CALL(env, info, JsAppServiceExtensionContext, OnStartAbility);
99 }
100
TerminateSelf(napi_env env,napi_callback_info info)101 static napi_value TerminateSelf(napi_env env, napi_callback_info info)
102 {
103 GET_NAPI_INFO_AND_CALL(env, info, JsAppServiceExtensionContext, OnTerminateSelf);
104 }
105
106 private:
107 std::weak_ptr<AppServiceExtensionContext> context_;
108 sptr<JsFreeInstallObserver> freeInstallObserver_ = nullptr;
109
OnTerminateSelf(napi_env env,NapiCallbackInfo & info)110 napi_value OnTerminateSelf(napi_env env, NapiCallbackInfo& info)
111 {
112 HITRACE_METER_NAME(HITRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
113 TAG_LOGI(AAFwkTag::APP_SERVICE_EXT, "TerminateSelf");
114 auto innerErrCode = std::make_shared<ErrCode>(ERR_OK);
115 NapiAsyncTask::ExecuteCallback execute = [weak = context_, innerErrCode]() {
116 auto context = weak.lock();
117 if (!context) {
118 TAG_LOGW(AAFwkTag::APP_SERVICE_EXT, "context released");
119 *innerErrCode = static_cast<int32_t>(AAFwk::ERR_INVALID_CONTEXT);
120 return;
121 }
122 *innerErrCode = context->TerminateSelf();
123 };
124 NapiAsyncTask::CompleteCallback complete =
125 [innerErrCode](napi_env env, NapiAsyncTask& task, int32_t status) {
126 if (*innerErrCode == ERR_OK) {
127 task.Resolve(env, CreateJsUndefined(env));
128 } else {
129 task.Reject(env, CreateJsErrorByNativeErr(env, *innerErrCode));
130 }
131 };
132
133 napi_value lastParam = (info.argc == ARGC_ZERO) ? nullptr : info.argv[INDEX_ZERO];
134 napi_value result = nullptr;
135 NapiAsyncTask::ScheduleHighQos("JsAppServiceExtensionContext::TerminateSelf",
136 env, CreateAsyncTaskWithLastParam(env, lastParam, std::move(execute), std::move(complete), &result));
137 return result;
138 }
139
CheckConnectionParam(napi_env env,napi_value value,sptr<JSAppServiceExtensionConnection> & connection,AAFwk::Want & want,int32_t accountId=-1) const140 bool CheckConnectionParam(napi_env env, napi_value value,
141 sptr<JSAppServiceExtensionConnection>& connection, AAFwk::Want& want, int32_t accountId = -1) const
142 {
143 if (!CheckTypeForNapiValue(env, value, napi_object)) {
144 TAG_LOGE(AAFwkTag::APP_SERVICE_EXT, "get connection obj failed");
145 return false;
146 }
147 connection->SetJsConnectionObject(value);
148 ConnectionKey key;
149 {
150 std::lock_guard guard(g_connectsMutex);
151 key.id = g_serialNumber;
152 key.want = want;
153 key.accountId = accountId;
154 connection->SetConnectionId(key.id);
155 g_connects.emplace(key, connection);
156 if (g_serialNumber < INT32_MAX) {
157 g_serialNumber++;
158 } else {
159 g_serialNumber = 0;
160 }
161 }
162 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "Unable to find connection, make new one");
163 return true;
164 }
165
GetConnectAbilityExecFunc(const AAFwk::Want & want,sptr<JSAppServiceExtensionConnection> connection,int64_t connectId,std::shared_ptr<int> innerErrorCode)166 NapiAsyncTask::ExecuteCallback GetConnectAbilityExecFunc(const AAFwk::Want &want,
167 sptr<JSAppServiceExtensionConnection> connection, int64_t connectId, std::shared_ptr<int> innerErrorCode)
168 {
169 return [weak = context_, want, connection, connectId, innerErrorCode]() {
170 TAG_LOGI(AAFwkTag::APP_SERVICE_EXT, "Connect ability: %{public}d",
171 static_cast<int32_t>(connectId));
172
173 auto context = weak.lock();
174 if (!context) {
175 TAG_LOGE(AAFwkTag::APP_SERVICE_EXT, "context released");
176 *innerErrorCode = static_cast<int>(AbilityErrorCode::ERROR_CODE_INVALID_CONTEXT);
177 return;
178 }
179
180 *innerErrorCode = context->ConnectAbility(want, connection);
181 };
182 }
183
OnConnectAbility(napi_env env,NapiCallbackInfo & info)184 napi_value OnConnectAbility(napi_env env, NapiCallbackInfo& info)
185 {
186 HITRACE_METER_NAME(HITRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
187 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "called");
188 // Check params count
189 if (info.argc < ARGC_TWO) {
190 TAG_LOGE(AAFwkTag::APP_SERVICE_EXT, "invalid argc");
191 ThrowTooFewParametersError(env);
192 return CreateJsUndefined(env);
193 }
194 // Unwrap want and connection
195 AAFwk::Want want;
196 sptr<JSAppServiceExtensionConnection> connection = new JSAppServiceExtensionConnection(env);
197 if (!AppExecFwk::UnwrapWant(env, info.argv[0], want)) {
198 ThrowInvalidParamError(env, "Parse param want failed, must be a Want.");
199 return CreateJsUndefined(env);
200 }
201 if (!CheckConnectionParam(env, info.argv[1], connection, want)) {
202 ThrowInvalidParamError(env, "Parse param options failed, must be a ConnectOptions.");
203 return CreateJsUndefined(env);
204 }
205 int64_t connectId = connection->GetConnectionId();
206 auto innerErrorCode = std::make_shared<int>(ERR_OK);
207 auto execute = GetConnectAbilityExecFunc(want, connection, connectId, innerErrorCode);
208 NapiAsyncTask::CompleteCallback complete = [connection, connectId, innerErrorCode](napi_env env,
209 NapiAsyncTask& task, int32_t status) {
210 if (*innerErrorCode == 0) {
211 TAG_LOGI(AAFwkTag::APP_SERVICE_EXT, "Connect ability success");
212 task.ResolveWithNoError(env, CreateJsUndefined(env));
213 return;
214 }
215
216 TAG_LOGE(AAFwkTag::APP_SERVICE_EXT, "Connect ability failed");
217 int32_t errcode = static_cast<int32_t>(AbilityRuntime::GetJsErrorCodeByNativeError(*innerErrorCode));
218 if (errcode) {
219 connection->CallJsFailed(errcode);
220 RemoveConnection(connectId);
221 }
222 };
223 napi_value result = nullptr;
224 NapiAsyncTask::ScheduleHighQos("JSAppServiceExtensionConnection::OnConnectAbility",
225 env, CreateAsyncTaskWithLastParam(env, nullptr, std::move(execute), std::move(complete), &result));
226 return CreateJsValue(env, connectId);
227 }
228
FindConnection(int64_t connectId,AAFwk::Want & want,sptr<JSAppServiceExtensionConnection> & connection,int32_t & accountId) const229 void FindConnection(int64_t connectId, AAFwk::Want& want, sptr<JSAppServiceExtensionConnection>& connection,
230 int32_t &accountId) const
231 {
232 TAG_LOGI(AAFwkTag::APP_SERVICE_EXT, "FindConnection connectId:%{public}" PRIu64 "", connectId);
233 std::lock_guard guard(g_connectsMutex);
234 auto item = std::find_if(g_connects.begin(),
235 g_connects.end(),
236 [&connectId](const auto &obj) {
237 return connectId == obj.first.id;
238 });
239 if (item != g_connects.end()) {
240 // match id
241 want = item->first.want;
242 connection = item->second;
243 accountId = item->first.accountId;
244 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "find conn ability exist");
245 }
246 return;
247 }
248
OnDisconnectAbility(napi_env env,NapiCallbackInfo & info)249 napi_value OnDisconnectAbility(napi_env env, NapiCallbackInfo& info)
250 {
251 HITRACE_METER_NAME(HITRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
252 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "called");
253 if (info.argc < ARGC_ONE) {
254 TAG_LOGE(AAFwkTag::APP_SERVICE_EXT, "invalid argc");
255 ThrowTooFewParametersError(env);
256 return CreateJsUndefined(env);
257 }
258 int64_t connectId = -1;
259 if (!AppExecFwk::UnwrapInt64FromJS2(env, info.argv[INDEX_ZERO], connectId)) {
260 ThrowInvalidParamError(env, "Parse param connection failed, must be a number.");
261 return CreateJsUndefined(env);
262 }
263 AAFwk::Want want;
264 sptr<JSAppServiceExtensionConnection> connection = nullptr;
265 int32_t accountId = -1;
266 FindConnection(connectId, want, connection, accountId);
267 // begin disconnect
268 auto innerErrCode = std::make_shared<ErrCode>(ERR_OK);
269 NapiAsyncTask::ExecuteCallback execute = [weak = context_, want, connection, accountId, innerErrCode]() {
270 auto context = weak.lock();
271 if (!context) {
272 TAG_LOGW(AAFwkTag::APP_SERVICE_EXT, "context released");
273 *innerErrCode = static_cast<int32_t>(AAFwk::ERR_INVALID_CONTEXT);
274 return;
275 }
276 if (!connection) {
277 TAG_LOGW(AAFwkTag::APP_SERVICE_EXT, "null connection");
278 *innerErrCode = ERR_INVALID_VALUE;
279 return;
280 }
281 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "context->DisconnectAbility");
282 *innerErrCode = context->DisconnectAbility(want, connection, accountId);
283 };
284 NapiAsyncTask::CompleteCallback complete =
285 [innerErrCode](napi_env env, NapiAsyncTask& task, int32_t status) {
286 if (*innerErrCode == ERR_OK) {
287 task.Resolve(env, CreateJsUndefined(env));
288 } else {
289 task.Reject(env, CreateJsErrorByNativeErr(env, *innerErrCode));
290 }
291 };
292 napi_value lastParam = (info.argc == ARGC_ONE) ? nullptr : info.argv[INDEX_ONE];
293 napi_value result = nullptr;
294 NapiAsyncTask::Schedule("JSAppServiceExtensionConnection::OnDisconnectAbility",
295 env, CreateAsyncTaskWithLastParam(env, lastParam, std::move(execute), std::move(complete), &result));
296 return result;
297 }
298
CheckStartAbilityInputParam(napi_env env,NapiCallbackInfo & info,AAFwk::Want & want,AAFwk::StartOptions & startOptions,size_t & unwrapArgc) const299 bool CheckStartAbilityInputParam(napi_env env, NapiCallbackInfo& info,
300 AAFwk::Want& want, AAFwk::StartOptions& startOptions, size_t& unwrapArgc) const
301 {
302 if (info.argc < ARGC_ONE) {
303 return false;
304 }
305 unwrapArgc = ARGC_ZERO;
306 if (!AppExecFwk::UnwrapWant(env, info.argv[INDEX_ZERO], want)) {
307 return false;
308 }
309 ++unwrapArgc;
310 if (info.argc > ARGC_ONE && CheckTypeForNapiValue(env, info.argv[INDEX_ONE], napi_object)) {
311 AppExecFwk::UnwrapStartOptions(env, info.argv[INDEX_ONE], startOptions);
312 unwrapArgc++;
313 }
314 return true;
315 }
316
OnStartAbility(napi_env env,NapiCallbackInfo & info)317 napi_value OnStartAbility(napi_env env, NapiCallbackInfo& info)
318 {
319 HITRACE_METER_NAME(HITRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
320 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "StartAbility");
321
322 size_t unwrapArgc = 0;
323 AAFwk::Want want;
324 AAFwk::StartOptions startOptions;
325 if (info.argc == ARGC_ZERO) {
326 TAG_LOGE(AAFwkTag::APP_SERVICE_EXT, "invalid arg");
327 ThrowTooFewParametersError(env);
328 return CreateJsUndefined(env);
329 }
330 if (!CheckStartAbilityInputParam(env, info, want, startOptions, unwrapArgc)) {
331 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "Failed, input param type invalid");
332 ThrowInvalidParamError(env, "Parse param want failed, want must be Want");
333 return CreateJsUndefined(env);
334 }
335 auto innerErrCode = std::make_shared<ErrCode>(ERR_OK);
336 NapiAsyncTask::ExecuteCallback execute =
337 [weak = context_, want, startOptions, unwrapArgc, innerErrCode]() {
338 auto context = weak.lock();
339 if (!context) {
340 TAG_LOGI(AAFwkTag::APP_SERVICE_EXT, "null context");
341 *innerErrCode = static_cast<int>(AbilityErrorCode::ERROR_CODE_INVALID_CONTEXT);
342 return;
343 }
344 *innerErrCode = (unwrapArgc == ARGC_ONE) ? context->StartAbility(want) :
345 context->StartAbility(want, startOptions);
346 };
347 NapiAsyncTask::CompleteCallback complete =
348 [innerErrCode](napi_env env, NapiAsyncTask& task, int32_t status) {
349 if (*innerErrCode == ERR_OK) {
350 task.ResolveWithNoError(env, CreateJsUndefined(env));
351 } else if (*innerErrCode == static_cast<int32_t>(AbilityErrorCode::ERROR_CODE_INVALID_CONTEXT)) {
352 task.Reject(env, CreateJsError(env, AbilityErrorCode::ERROR_CODE_INVALID_CONTEXT));
353 } else {
354 task.Reject(env, CreateJsErrorByNativeErr(env, *innerErrCode));
355 }
356 };
357
358 napi_value lastParam = nullptr;
359 napi_value result = nullptr;
360 NapiAsyncTask::ScheduleHighQos("JSAppServiceExtensionContext::OnStartAbility",
361 env, CreateAsyncTaskWithLastParam(env, lastParam, std::move(execute), std::move(complete), &result));
362 return result;
363 }
364 };
365 } // namespace
366
CreateJsAppServiceExtensionContext(napi_env env,std::shared_ptr<AppServiceExtensionContext> context)367 napi_value CreateJsAppServiceExtensionContext(napi_env env, std::shared_ptr<AppServiceExtensionContext> context)
368 {
369 TAG_LOGD(AAFwkTag::APP_SERVICE_EXT, "called");
370 std::shared_ptr<OHOS::AppExecFwk::AbilityInfo> abilityInfo = nullptr;
371 if (context) {
372 abilityInfo = context->GetAbilityInfo();
373 }
374 napi_value object = CreateJsExtensionContext(env, context, abilityInfo);
375
376 std::unique_ptr<JsAppServiceExtensionContext> jsContext = std::make_unique<JsAppServiceExtensionContext>(context);
377 napi_wrap(env, object, jsContext.release(), JsAppServiceExtensionContext::Finalizer, nullptr, nullptr);
378
379 const char *moduleName = "JsAppServiceExtensionContext";
380 BindNativeFunction(
381 env, object, "connectServiceExtensionAbility", moduleName, JsAppServiceExtensionContext::ConnectAbility);
382 BindNativeFunction(
383 env, object, "disconnectServiceExtensionAbility", moduleName, JsAppServiceExtensionContext::DisconnectAbility);
384 BindNativeFunction(env, object, "startAbility", moduleName, JsAppServiceExtensionContext::StartAbility);
385 BindNativeFunction(
386 env, object, "terminateSelf", moduleName, JsAppServiceExtensionContext::TerminateSelf);
387 return object;
388 }
389
390 } // namespace AbilityRuntime
391 } // namespace OHOS
392