@webority / mobile-core
web React Native应用的平台层:HTTP客户端、认证和令牌存储、配置、
日志,格式化程序,验证程序,网络状态,权限,存储,图像压缩和
版本检查。 没有用户界面 -组件生活在
@webority/mobile-ui.
npm install @webority/mobile-core
运行时依赖关系: axios 和 libphonenumber-js。所需的对等体有 expo,
expo-application, expo-network, expo-secure-store (HTTP客户端从
它), expo-sqlite。其他本地对等体都是可选的,只能通过自己的导入来访问
下面的路径,因此包安装和运行时不需要它们。 react-native-keychain 没有
不再使用;令牌存储移至 expo-secure-store.
完整的文档位于存储库的 docs/ 目录中。
导入路径
任何需要原生包的东西都在它自己的路径后面,所以一个应用只打包它需要的东西 进口:
import { Permissions } from '@webority/mobile-core/permissions';
import { Geolocation } from '@webority/mobile-core/geolocation';
import { openDatabase } from '@webority/mobile-core/sqlite';
import { downloadFile } from '@webority/mobile-core/download';
import { shareFile } from '@webority/mobile-core/share';
import { fetchAttachment, fetchPreviewAttachment, previewKind } from '@webority/mobile-core/attachments';
import { getOrCreateInstallationId, clearTokensOnFirstLaunch } from '@webority/mobile-core/installation';
import { requestReviewAfterSuccess } from '@webority/mobile-core/review';
import { initPush, enablePush, usePush } from '@webority/mobile-core/push';
import { definePushActionTask } from '@webority/mobile-core/push/task';
import { compressImage } from '@webority/mobile-core/compressor';
import { initOtaUpdates, useOtaUpdates } from '@webority/mobile-core/updates';
import { createApiBaseQuery, createAppApi, setupApiListeners } from '@webority/mobile-core/query';
Metro解析是静态的,所以任何可以从主桶访问的东西都被绑定 每一个消费者。让他们远离这些是让他们的同伴真正可以选择的原因。
/query 为RTK查询层;它需要 @reduxjs/toolkit (2.12或更高版本)和 react-redux,
都是可选的对等体。一个 createApi 每个应用程序,构建与 createAppApi 基本查询结束
库的客户端,所以刷新,重试,脱机检查和 extractApiError 失败
形状停留在一个地方。从来没有一个 fetchBaseQuery.
// services/api/apiSlice.ts
import { publicClient, userClient } from '@webority/mobile-core';
import { createApiBaseQuery, createAppApi, buildListQuery, providesPagedList,
type PagedDataObject } from '@webority/mobile-core/query';
export const apiSlice = createAppApi({
reducerPath: 'api',
baseQuery: createApiBaseQuery({ client: userClient, publicClient }),
tagTypes: ['Project'],
endpoints: (build) => ({
getProjects: build.query<PagedDataObject<Project>, { page: number }>({
query: ({ page }) => ({ url: '/api/projects', params: buildListQuery({ page, pageSize: 20 }) }),
providesTags: providesPagedList('Project')
})
})
});
// store.ts: add apiSlice.reducer and apiSlice.middleware, then setupApiListeners(store.dispatch)
setupApiListeners 将应用程序映射到前台(AppState)到RTK查询的焦点,
并将本馆的网络状态分为线上和线下;RTK自己的默认值 window
从未在设备上触发的事件。有标记的呼叫 silent: true的端点
extraOptions: { silent: true }, silent 在 action.meta.baseQueryMeta,其中
吐司中间件跳过错误。
/push 为手机注册推送通知。它需要 expo-notifications,可选
同行和报告 unavailable 没有它或在世博会上。呼叫 initPush 在开始的时候
getInstallationId, register, unregister 和 onOpen; enablePush 请求许可,发送
本地令牌(Fcm 在Android上, Apns 在iOS上)和bundle id to register,
当令牌更改时重新注册; disablePush 调用 unregister 开关只推一次
它成功了(失败拒绝并留下push)。 resetPush 把电话按下,没有
服务器调用并丢弃本机令牌,用于没有会话剩余的签出。 onOpen 接收
点击通知的数据为字符串,包括启动应用的点击。 usePush 给出了一个
设置开关 status 和 enabled。库不记得这个开关:一个应用程序
保持来电推送 enablePush 每次出发。 setBadgeCount(count) 设置应用图标徽章。 dismissAllNotifications() 清除通知托盘中的每个通知;它从不抛出,并在warn处记录失败。
enablePush({ prompt: false })从不显示权限提示:已授予的电话 静默注册,并且任何其他都以其状态(undetermined当人已不在 有人问过,denied当拒绝时,提示仍然可以显示,blocked当只有 设置可以打开它)。在签到时打电话,然后打电话enablePush()当 人到了问问题有意义的时候。- 每个注册设备都携带
timeZone,手机的IANA区域(Asia/Kolkata),或为空 平台不报告任何信息,因此服务器可以在用户的本地时间发送信息。 - 每个注册设备都携带
apnsEnvironment:Development或者Production在iOS上,请阅读 从构建的aps-environment(expo-application),因此服务器将每个电话发送到 apn网关其签名接受。它在Android上为空,在iOS上无法读取时为空 模拟器);然后服务器使用它自己的设置。 onReceive(handler)调用handler({ notificationId, title, body, data })为每一个 通知在应用打开时到达,显示或不显示,所以应用可以刷新列表或 未读计数。它返回一个删除处理程序的函数。通知到达 背景没有达到它;它的水龙头仍然是onOpen.
离线副本
createPersistedQueries 将所选端点的答案保存在手机上,因此打开了一个屏幕
“无连接”显示最后一次加载数据的时间。列出的端点保存到
Storage 当它们装载时; restore 一开始就放回去。
- 通过
extractRehydrationInfo来createAppApi. - 添加
middleware在api的中间件之后。 - 呼叫
restore(store.dispatch)在创建存储之后。 - 呼叫
clear()签到,所以下一个接电话的人永远不会看到上一个人的数据。
// services/api/offlineQueries.ts
export const offlineQueries = createPersistedQueries({
storageKey: 'offline.queries',
endpoints: ['getProjects']
});
// services/api/apiSlice.ts: add extractRehydrationInfo: offlineQueries.extractRehydrationInfo
// store.ts: reducer: { [apiSlice.reducerPath]: apiSlice.reducer }
// middleware: (getDefault) => getDefault().concat(apiSlice.middleware, offlineQueries.middleware)
offlineQueries.restore(store.dispatch); // once, after the store exists
// sign-out
await offlineQueries.clear();
还原后的答案立即显示,无需取回,所以设置 refetchOnMountOrArgChange: true 在每一个
保留端点:挂载的屏幕再次询问服务器并刷新已恢复的数据。
通知按钮
push命名一个类别;这个类别的按钮是在手机上注册的。在以下网址登记 每次开始,在两个平台上使用相同的id:
import { setCategories, notifyLocal } from '@webority/mobile-core/push';
await setCategories([
{
id: 'approval',
actions: [
{ id: 'approve', title: 'Approve', opensApp: false },
{ id: 'reject', title: 'Reject', destructive: true }
]
},
{
id: 'reply',
actions: [
{
id: 'reply',
title: 'Reply',
opensApp: false,
textInput: { submitTitle: 'Send', placeholder: 'Write a reply' }
}
]
}
]);
opensApp 默认为true; destructive 和 authenticationRequired (首先解锁)是iOS
只有。点击按钮到达 { actionId, userText, data, notificationId }: userText 是
回复文本, data push数据作为字符串, notificationId 通知的标识符为
电话。一个普通的水龙头仍然是 onOpen; 没有处理程序的按钮点击像普通点击一样打开。
要接收每个应用状态下的按钮点击,被杀死的应用包括call definePushActionTask
在根组件注册之前,在入口文件的模块范围内。它需要
expo-task-manager,可选的对等体(npx expo install expo-task-manager),所以它以自己的方式生活
自己的路:
// index.js
import { definePushActionTask } from '@webority/mobile-core/push/task';
import { notifyLocal } from '@webority/mobile-core/push';
definePushActionTask(async ({ actionId, userText, data }) => {
try {
await settle(actionId, userText, data);
} catch {
await notifyLocal({ title: 'Could not approve', body: 'Open the app to try again.' });
}
});
等待处理程序的promise,因此它的请求可以在后台工作结束之前完成。
onAction(handler) 从 /push 在JavaScript运行时为点击设置相同的处理程序
返回一个删除它的函数。在10秒内报告了两次回应(发射为,
侦听器和Android任务可以各自报告它运行一次。
- Android: 控件上的按钮点击时,后台通知任务运行处理程序
按钮只出现在通知上,博览会通知绘图
例子
notifyLocal). 这是一款应用程序,它可以从纯数据推送中获取自己的通知 每个按钮的意图都要用世博会的NotificationsService.createNotificationResponseIntent, 所以tap到达这个处理程序。 - iOS: 一个带有
opensApp: false在后台启动应用程序;响应 到达听众definePushActionTask设置。任务未在iOS上注册。
存储和网络状态保持在主桶上,但不加载自己的包。把它们连接在
启动与 setStorageImplementation 和 setNetworkStatusImplementation 如果你想要的话。
构建号标头和强制更新
两个HTTP客户端都发送 App-Build: <integer> 在每个请求上:应用程序的整个构建号
(VERSION_INTERNAL, Android版本码,iOS CFBundleVersion)。API将其与最小值进行比较
答案是426的旧建筑 client_update_required。这个数字来自
initWebority({ appBuild }) (setConfig({ appBuild })), else来自expo-application’s
nativeBuildVersion。如果没有整数,报头将被删除,并且服务器永远不会拒绝
没有它的请求。 Client-Build (<appVersion>+<commit>)不变。
两个HTTP客户端也发送 Client-App: <appId> 当配置有一个app id (initWebority({ appId }))。
它是应用程序的商店标识符,因此API可以区分相同客户端类型的应用程序。
版本层只来自于 Min-App-Version 响应头。当一个答复带着它,
useVersionCheck 转 isUpdateRequired 对于它下面的构建。一个426 client_update_required
不移动地板,所以没有标题的426离开地板。426永远不会
重试。
应用程序通过运行移动到该合约 webority upgrade 0.93.0。它重新命名旧的 X- 头
字符串到普通名称, ApiError.detail 来 diagnostic 和 detail.signOutReason 来
detail.code,并列出每个已删除的选项(correlationIdHeader, signOutReasonHeader),
一个人要删除的常量。
静默请求和日志编校
两个HTTP客户端都记录了一个失败的响应 Logger.error。对于一个呼叫失败的应用程序
处理自己,传递 skipErrorLog: true 在axios配置和客户端保持沉默:
await userClient.get('/status', { skipErrorLog: true });
Logger 在任何接收器看到它们之前隐藏每个消息和参数中的秘密: Bearer 令牌,
jwt形状的字符串,以及名为 token, accessToken, refreshToken,
password 和 authorization (任何情况下)的对象和JSON字符串。
configureLogger({ redactKeys }) 将应用程序键添加到内置屏蔽列表中,并且永远不会删除一个。 Logger.getBreadcrumbs() 返回最后50个面包屑,最老的放在第一位,已经编辑; configureLogger({ breadcrumbLimit }) 改变盖子。 initWebority 这是必需的 remoteLogger水槽,或者 null 对于没有崩溃报告的构建。这也是必需的 appId,商店id (APP_ID 在appsettings。Json,没有环境后缀),以及一个必需的 environment。它添加了环境的后缀(.development, .staging当结果与已安装的应用的id不同时,拒绝启动。
应用锁
@webority/mobile-core/appLock 当一个登录的人在锁定延迟(默认为5分钟)后返回时,锁定应用程序。 useAppLock 返回设置屏幕和锁定屏幕需要的内容:
import { useAppLock } from '@webority/mobile-core/appLock';
const { locked, enabled, biometricAvailable, availabilityKnown, setEnabled, unlock } = useAppLock({ active: isSignedIn });
availabilityKnown在第一次生物识别检查结束前都是假的。在那之前biometricAvailable还不是答案,所以屏幕应该等待,然后显示锁不可用或禁用其开关。setEnabled(next)和unlock(reason)返回一个AppLockResult:{ ok: true },或者{ ok: false, reason }有了理由cancelled,lockout,not-enrolled,not-available或者failed.policy覆盖延迟和两个存储键;默认值用于任何遗漏的键。
地铁桥
@webority/mobile-core/metro 构建Metro开发工具所读取的开发桥梁
globalThis.__METRO_BRIDGE__。应用程序在下面分配它 __DEV__; 建造者没有添加自己的检查。
import { createMetroBridge } from '@webority/mobile-core/metro';
if (__DEV__) {
globalThis.__METRO_BRIDGE__ = createMetroBridge({ store, navigationRef, signIn, signOut, isSignedIn });
}
storeRedux存储,通读getState写了一遍dispatch.navigationRef:导航容器的ref (isReady,getRootState,canGoBack,goBack,navigate)。signIn(request)解决了{ codeRequired };signOut()一旦会话被清除就解析;isSignedIn()返回一个布尔值。commands(可选):额外命名的命令。一个名字signIn,signOut或者navigate抛出 创建桥的时候。
这座桥暴露了 commands (signIn, signOut, navigate,以及额外费用), navigation
(getState,在导航器准备好之前,该值为空 goBack,返回 { wentBack }),
redux, auth (isSignedIn), push (notifyLocal 和 deviceToken,设备的推送令牌)。