npm package: @hadss/react_native_intents
Description: RN react_native interface package
Latest version: 1.0.0-rc.0

# @hadss/react_native_intents

## 介绍
为 React Native 提供意图框架分享能力，包括共享和删除意图。该库封装了鸿蒙insightIntent的相关功能，开发者可通过简洁接口在 React Native 中向意图框架共享意图。意图框架根据共享的数据学习规律，在合适的时机在系统入口将对应的InsightIntent推荐给用户。


## 工程目录

```
.
├─harmony                                             
│    ├─IntentsHO                                     // Harmony
│    |   └─intents.har                               // Turbo Modules接口har包
│    ├─IntentsOH                                     // OpenHarmony
│    │   └─intents.har                               // Turbo Modules接口har包
|    ├─intents
│    │  └─src
│    │      ├─main
│    │      │    └─ets
│    │      │      ├─IntentsModule.ets                // Turbo Modules接口ArkTS实现
│    │      │      ├─IntentsModulePackage.ets         // IntentsModuleFactory
│    │      │      └─IntentsModuleSpec.ts             // Turbo Modules接口
│    │      ├─HORealization                          
│    │      │      ├─IntentsModule.ets                // Harmony环境差异化源码
│    │      └─OHRealization 
│    │             └─IntentsModule.ets                // OpenHarmony环境差异化源码
|    └─build-profile.json5                            // 编译配置文件（多环境Harmony/OpenHarmony）
├─src 
│    ├─index.ts                                        // React Native 模块导出
│    └─turbo    
|       └─IntentsModule.ts                             // React Native Turbo Modules接口
```


## 安装与使用

进入到工程目录并输入以下命令：

<!-- tabs:start -->

**npm**

```bash
npm install @hadss/react_native_intents
```

**yarn**

```bash
yarn add @hadss/react_native_intents
```
<!-- tabs:end -->

## Link

目前OpenHarmony暂不支持AutoLink，所以Link步骤需要手动配置。

首先需要使用DevEco Studio打开项目里的OpenHarmony工程，在工程根目录的`oh-package.json5`添加overrides字段：

```json
{
  "overrides": {
    "@rnoh/react-native-openharmony" : "./react_native_openharmony"
  }
}
```


### 意图框架配置
**意图前置配置：**
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/intents-access-flow
开发者需要参照上述官方文档中的Intents Kit接入流程
进行操作：
1.选择特性确定意图
2.调试白名单申请
3.端侧意图调用调试工具申请
4.意图声明文件中注册意图
5.开发实现意图调用/意图共享
6.端到端验证特性
7.应用市场上架软件包（应用/元服务）
8.意图框架注册

本库涉及开发接入的主要工作在实现4、5两个步骤：
1.意图声明文件中注册意图（意图回调注册-鸿蒙实现）
2.实现意图调用（鸿蒙端实现）/意图共享（React Native实现）
其他工作请开发者自行申请完成

**1.意图声明文件中注册意图-鸿蒙端处理：**
参考文档：https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/intents-habit-rec-access-programme#section12621163216260
以歌曲续听推荐特性为例，首先要注册播放歌曲意图（PlayMusic），其他意图见各垂域意图Schema。开发者需要编辑对应的意图配置 PROJECT_HOME/entry/src/main/resources/base/profile/insight_intent.json文件，实现意图注册。

```json
{
  // 应用支持的意图列表
  // 必须声明应用支持插件包含的必选意图，应用上架时会进行校验
  "insightIntents": [
    {
      // 意图名称
      // 名称应当遵循意图框架规范，当前仅支持预置垂域意图，不允许自定义
      // 应用内意图名称唯一，不允许出现相同的名称定义
      "intentName": "PlayMusic",
      // 意图所属的垂域
      "domain": "MusicDomain",
      // 意图版本号
      // 插件引用意图时会校验该版本号，只有和插件定义的版本号一致才能正常调用
      "intentVersion": "1.0.1",
      // 意图调用逻辑入口
      // 根据意图调用文件实际路径和实际名称进行填写，此处文件仅做示意
      "srcEntry": "./ets/entryability/InsightIntentExecutorImpl.ets",
      "uiAbility": {
        // 意图所在module、ability，以及代码相对路径入口
        "ability": "EntryAbility",
        // UIAbility支持前后台两种执行模式
        "executeMode": [
          "background",
          "foreground"
        ]
      }
    }
  ]
}

```

**2.实现意图调用（鸿蒙端实现）/意图共享（React Native实现）**
**2.1 实现意图调用（鸿蒙端实现）**
意图执行组件为uiAbility的意图调用
当开发者注册的意图承载的运行组件为uiAbility时，开发者需要自己实现InsightIntentExecutor，上述意图声明文件中的srcEntry对应文件，并在对应回调实现打开落地页（点击推荐卡片跳转的界面）的能力，PlayMusic的意图调用字段定义见各垂域意图Schema：https://developer.huawei.com/consumer/cn/doc/service/intents-schema-0000001901962713
具体步骤如下方示例代码：
继承InsightIntentExecutor。
重写对应方法，例如目标拉起前台页面，则可重写onExecuteInUIAbilityForegroundMode方法。
通过意图名称，识别播放歌曲意图（PlayMusic），在对应的方法中传递意图参数（param），并拉起对应落地页（如歌曲落地页）。

```ts
import { insightIntent, InsightIntentExecutor } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';


/**
 * 意图调用样例
 */
export default class InsightIntentExecutorImpl extends InsightIntentExecutor {
  private static readonly PLAY_MUSIC = 'PlayMusic';
  /**
   * override 执行前台UIAbility意图
   *
   * @param name 意图名称
   * @param param 意图参数
   * @param pageLoader 窗口
   * @returns 意图调用结果
   */
  onExecuteInUIAbilityForegroundMode(name: string, param: Record<string, Object>, pageLoader: window.WindowStage):
    Promise<insightIntent.ExecuteResult> {
    // 根据意图名称分发处理逻辑。接入方可根据实际业务实现页面跳转
    switch (name) {
      case InsightIntentExecutorImpl.PLAY_MUSIC:
        return this.playMusic(param, pageLoader);
      default:
        break;
    }
    return Promise.resolve({
      code: -1,
      result: {
        message: 'unknown intent'
      }
    } as insightIntent.ExecuteResult)
  }
  /**
   * 实现调用播放歌曲功能
   *
   * @param param 意图参数
   * @param pageLoader 窗口
   */
  private playMusic(param: Record<string, Object>, pageLoader: window.WindowStage): Promise<insightIntent.ExecuteResult> {
    return new Promise((resolve, reject) => {
      let para: Record<string, string> = {
        'result': JSON.stringify(param)
      };
      let localStorage: LocalStorage = new LocalStorage(para);
      // TODO 实现意图调用，loadContent的入参为歌曲落地页路径，例如：pages/Index
      pageLoader.loadContent('pages/Index', localStorage)
        .then(() => {
          let entityId: string = (param.items as Array<object>)?.[0]?.['entityId'];
          // TODO 调用成功的情况，此处可以打印日志
          resolve({
            code: 0,
            result: {
              message: 'Intent execute succeed'
            }
          });
        })
        .catch((err: BusinessError) => {
          // TODO 调用失败的情况
          resolve({
            code: -1,
            result: {
              message: 'Intent execute failed'
            }
          })
        });
    })
  }
}

```
**2.2 意图共享（React Native实现）**
通过库中接口在React Native侧进行意图分享、删除的操作
代码案例：
```tsx
import React from 'react';
import {
  Alert,
  SafeAreaView,
  StyleSheet,
  Text,
  TouchableOpacity,
  useColorScheme,
  View,
} from 'react-native';

import {
  Colors,
} from 'react-native/Libraries/NewAppScreen';

import { Intents } from "@hadss/react_native_intents";

function App(): JSX.Element {
  const isDarkMode = useColorScheme() === 'dark';

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
    flex: 1
  };

  const handleGetSid = async () => {
    Alert.alert(`${JSON.stringify(await Intents.getSid(true))}`);
  };
  const handleShareIntentPlayMusic = async () => {
    await Intents.shareIntent([
      {
        "intentName": "PlayMusic",
        "intentVersion": "1.0",
        "identifier": "52dac3b0-6520-4974-81e5-25f0879449b5",
        "intentActionInfo": {
          "actionMode": "EXECUTED",
          "executedTimeSlots": {
            "executedStartTime": 1747328400000,
            "executedEndTime": 1747328452000
          },
          "currentPercentage": 50
        },
        "intentEntityInfo": {
          "entityName": "Music",
          "entityId": "C10194368",
          "entityGroupId": "C10194321312",
          "displayName": "测试歌曲",
          "description": "NA",
          "logoURL": "https://www-file.huawei.com/-/media/corporate/images/home/logo/huawei_logo.png",
          "keywords": [
            "华为音乐",
            "化妆"
          ],
          "rankingHint": 99,
          "expirationTime": 1747328452000,
          "metadataModificationTime": 1747328452000,
          "activityType": [
            "1",
            "2",
            "3"
          ],
          "artist": [
            "测试歌手1",
            "测试歌手2"
          ],
          "lyricist": [
            "测试词作者1",
            "测试词作者2"
          ],
          "composer": [
            "测试曲作者1",
            "测试曲作者2"
          ],
          "albumName": "测试专辑",
          "duration": 244000,
          "playCount": 100000,
          "musicalGenre": [
            "流行",
            "华语",
            "金曲",
            "00后"
          ],
          "isPublicData": false
        }
      }
    ]).then(() => {
      Alert.alert(`意图分享成功`);
    })
  };

  const handleDeleteEntity = async () => {
    try {
      await Intents.deleteEntity('Music', ['C10194368'])
      Alert.alert(`意图deleteEntity成功`);
    } catch (error) {
      Alert.alert(`意图deleteEntity失败,${JSON.stringify(error)}`);
    }
  };

  const handleDeleteIntent = async () => {
    try {
      await Intents.deleteIntent('PlayMusic')
      Alert.alert(`意图deleteIntent成功`);
    } catch (error) {
      Alert.alert(`意图deleteIntent失败,${JSON.stringify(error)}`);
    }
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <View style={styles.container}>
        {/* 按钮1 GetSid */}
        <View style={styles.buttonGroup}>
          <Text style={styles.label}>GetSid</Text>
          <TouchableOpacity style={styles.button}
            onPress={() => {
              handleGetSid()
            }} >
            <Text style={styles.buttonText}>GetSid</Text>
          </TouchableOpacity>
        </View>

        {/* 按钮2 PlayMusic */}
        <View style={styles.buttonGroup}>
          <Text style={styles.label}>ShareIntent</Text>
          <TouchableOpacity style={styles.button}
            onPress={() => {
              handleShareIntentPlayMusic()
            }}>
            <Text style={styles.buttonText}>ShareIntent类型: PlayMusic</Text>
          </TouchableOpacity>
        </View>

        {/* 按钮3 DeleteEDeleteIntentntity */}
        <View style={styles.buttonGroup}>
          <Text style={styles.label}>DeleteIntent</Text>
          <TouchableOpacity style={styles.button}
            onPress={() => {
              handleDeleteIntent()
            }} >
            <Text style={styles.buttonText}>DeleteIntent</Text>
          </TouchableOpacity>
        </View>

        {/* 按钮4 DeleteEntity*/}
        <View style={styles.buttonGroup}>
          <Text style={styles.label}>DeleteEntity</Text>
          <TouchableOpacity style={styles.button}
            onPress={() => {
              handleDeleteEntity()
            }} >
            <Text style={styles.buttonText}>DeleteEntity</Text>
          </TouchableOpacity>
        </View>
      </View>
    </SafeAreaView>
  );
}

// 样式
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  buttonGroup: {
    marginVertical: 12,
    alignItems: 'center'
  },
  label: {
    fontSize: 14,
    color: '#666',
    marginBottom: 8
  },
  button: {
    backgroundColor: '#007AFF',
    paddingVertical: 12,
    paddingHorizontal: 32,
    borderRadius: 25
  },
  buttonText: {
    color: 'white',
    fontSize: 16
  }
});

export default App;

```


### 引入鸿蒙端代码

使用前需要在EntryAbility.ets文件中的合适位置，缓存IntentsContext,以供意图turoModule内方法使用
例如：
```ts
import { common } from '@kit.AbilityKit';
import { RNAbility } from '@rnoh/react-native-openharmony';

const TAG: string = 'EntryAbility';

export default class EntryAbility extends RNAbility {
  getPagePath() {
    AppStorage.setOrCreate('IntentsContext', this.context as common.Context)
    return 'pages/Index';
  }
}
```

目前有两种方法：

1. **通过har包引入（在IDE完善相关功能后该方法会被遗弃，目前首选此方法）。**

    >**说明：**
    >har包位于三方库安装路径的`harmony`文件夹下。

    a. 打开`entry/oh-package.json5`，添加以下依赖：

    ```
    "dependencies":{
        "@rnoh/react-native-openharmony": "file:../react_native_openharmony",
        "@hadss/react_native_intents": "file:../../node_modules/@hadss/react_native_intents/harmony/IntentsHO/intents.har",
      }
    ```

    b. 配置CMakeLists和引入RNOHGeneratedPackage：

    打开`entry/src/main/cpp/CMakeLists.txt`，添加：

    ```diff
    project(rnapp)
    cmake_minimum_required(VERSION 3.4.1)
    set(CMAKE_SKIP_BUILD_RPATH TRUE)
    set(OH_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
    set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
    
    set(RNOH_CPP_DIR "${OH_MODULE_DIR}/@rnoh/react-native-openharmony/src/main/cpp")
    set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated")
    set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
    set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
    add_compile_definitions(WITH_HITRACE_SYSTRACE)
    set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use
    + file(GLOB GENERATED_CPP_FILES "./generated/*.cpp")
    
    add_subdirectory("${RNOH_CPP_DIR}" ./rn)
    
    add_library(rnoh_app SHARED
    +    ${GENERATED_CPP_FILES}
        "./PackageProvider.cpp"
        "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
    )
    
    target_link_libraries(rnoh_app PUBLIC rnoh)
    ```

    c. 打开`entry/src/main/cpp/PackageProvider.cpp`，添加：

    ```diff
    #include "RNOH/PackageProvider.h"
    + #include "generated/RNOHGeneratedPackage.h"
    
    using namespace rnoh;
    
    std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
        return {
    +        std::make_shared<RNOHGeneratedPackage>(ctx)
        };
    }
    ```

    d. 在ArkTs侧引入AvoidModulePackage：

    打开`entry/src/main/ets/RNPackagesFactory.ts`，添加：

    ```diff
      ...
    + import { IntentsModulesPackage } from '@hadss/react_native_intents/ts';;
    
    export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
      return [
    +   new IntentsModulePackage(ctx),
      ];
    }
    ```

    e. 运行：

    点击右上角的`sync`按钮

    或者在终端执行：

    ```bash
    cd entry
    ohpm install
    ```
    然后编译、运行即可。
> **说明**
> 若项目启动时报错：can not find record '&@rnoh/react-native-openharmony/generated/ts&X.X.X'。需在entry\oh_modules@rnoh\react-native-openharmony\ts.ts文件中添加export * from './generated/ts'，并删除.cxx文件夹、build文件夹，然后执行sync操作同步代码。

2. **直接链接源码。**

    如需使用直接链接源码，请参考[直接链接源码说明](https://gitee.com/react-native-oh-library/usage-docs/blob/master/zh-cn/link-source-code.md)。

##  API

>**说明：**
>"Platform"列表示支持的平台，All表示支持所有平台。


### 意图框架接口
**Intents**
| Name                | Description                                          | Type     | Platform  |
| ------------------- | -----------------------------------------------------| -------- | --------- |
| shareIntent         | 共享已执行或预期的InsightIntent。                      | (insights: InsightIntent[])=>Promise<void> | OpenHarmony |
| deleteIntent        | 删除InsightIntent。如果设置了identifiers参数，则删除intent名称下的identifiers对应的记录。否则，删除意图名称下的所有记录。intentName:string 表示意图的名称;identifiers:string[] 表示意图的标识符。  | (intentName: string, identifiers?: string[])=>Promise<void> | OpenHarmony |
| deleteEntity        | 按实体名称下的实体ID删除。 entityName:string 表示实体的名称;entityIds:string[] 表示意图实体的标识符。| (entityName: string, entityIds: string[])=>Promise<void> | OpenHarmony |
| getSid              | 获取Service Open ID。renew: boolean 是否强制从云端获取新的服务开放ID。如果为true，从云端获取新的服务开放ID；如果为false，则优先获取本地存储的服务开放ID。 | (renew: boolean)=>Promise<string> | OpenHarmony |

**InsightIntent**

| Name               | Description                                                      | Type       | Platform  |
| :----------------- | :----------------------------------------------------------------| :--------- | :-------- |
| intentName         | 表示意图名称，例如：'PlayMusic'。                                  | string     | OpenHarmony |
| intentVersion      | 表示意图版本，当前为初始版本'1.0'。                                 | string     | OpenHarmony |
| identifier         | 表示意图的标识符，开发者按照自身业务需要生成，作为单条共享记录的唯一标识符。该字段的生成方式可参考UUID生成方式，或基于时间戳生成随机字符串。例如：'52dac3b0-6520-4974-81e5-25f0879449b5'。                          | string     | OpenHarmony |
| intentActionInfo   | 表示意图的执行信息，例如：已执行或预测将要执行意图的时间。             | IntentActionInfo | OpenHarmony |
| intentEntityInfo   | 表示意图的实体信息，包含行为和事件。                                 | IntentEntityInfo | OpenHarmony |

**IntentActionInfo**

| Name               | Description                                                      | Type       | Platform  |
| :----------------- | :----------------------------------------------------------------| :--------- | :-------- |
| [key: string]      | 表示值的类型为包含一个或多个键值对的对象。其中键的类型为字符，值的类型为Object，键和值的取值范围因意图名称而异，具体请参考各垂域意图Schema。                                                                                | object     | OpenHarmony |



**IntentEntityInfo**

| Name               | Description                                                      | Type       | Platform  |
| :----------------- | :----------------------------------------------------------------| :--------- | :-------- |
| entityId           | 	表示意图实体的标识符，开发者按照自身业务需要生成，作为单条实体的唯一标识符。该字段的生成方式可参考UUID生成方式，或基于时间戳生成随机字符串。例如：'C10194368'。                                                                           | string     | OpenHarmony |
| entityName         | 表示意图实体的名称。取值请参考各垂域意图Schema。                     | string     | OpenHarmony |
| [key: string]      | 意图实体的其他信息，表示为一个或多个键值对。其中键的类型为字符，值的类型为Object，键和值的取值范围因意图名称而异，具体请参考各垂域意图Schema。                                                                                 | object     | OpenHarmony |

各垂域意图Schema:https://developer.huawei.com/consumer/cn/doc/service/intents-schema-0000001901962713
意图配置接口参考：https://developer.huawei.com/consumer/cn/doc/harmonyos-references/intents-arkts-api-insightintent#section13611343185915

### 依赖
本示例依赖@ohos/hvigor-ohos-plugin，且最低版本为4.3.0。
使用DevEco Studio版本大于本示例推荐版本，请根据 DevEco Studio 提示更新 hvigor 插件版本。
SID获取需联网、登录华为账号并同意小艺建议的用户协议和隐私政策。

### 约束与限制
意图共享和意图调用的测试，当前无法由开发者独立完成，烦请根据Intents Kit接入流程，通过邮箱向华为意图框架接口人提交验收申请，由接口人配合开发者一同完成测试验收。
本示例仅支持标准系统上运行，支持设备：Phone | Tablet。
地区限制：仅支持中国境内（不包含中国香港、中国澳门、中国台湾）提供服务。
操作系统限制：HarmonyOS 5.0及以上。
DevEco Studio版本：DevEco Studio NEXT Developer Beta1及以上。
HarmonyOS SDK版本：HarmonyOS NEXT Developer Beta1 SDK及以上。