mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-08-17 19:07:12 +02:00
feat(mobile): add NFC plugin (#830)
* feat: scaffold NFC plugin, initial iOS code * adjust script paths (api example) * update entitlements & plist * update class name * update api * sketch api, remove desktop * update response data * add write fn * remove commands * fixes for write mode * check nfc state before using the APIs * fix(example): downgrade internal-ip to v7 * feat: typed iOS arguments * update swift requirement * android updates * update tauri * fix icon * update example * fix build * fix notification example * fix clipboard * fix ios notification build * fix info.plist * update tauri * add change file * fmt * update to new args class syntax :( [skip ci] * add lang code handling in RTD_TEXT helper (written payload is broken without it) * update nfc to latest tauri. use tauri from git * update lockfile * android: add initial nfc writer implementation * check sdk version for pendingintent flag * quicksaving basic ndef reading that doesn't crash * add basic ndef reading (android) * small cleanup * change pending action type * validate available state * gradle 8.0.0 * use session class * implement keep session alive * fix notification crash?? * remove dox feature, fix breaking changes * update dependencies * fix shell tests [skip ci] * fmt [skip ci] * type safe args * scan kind options * commit .idea files * update api * update example * fix app check * alertmessage options for iOS * default to tag on example * fix(ios): always close session on write, remove keepsessionalive option * add kind input to write options * empty records if message not found * fill tag metadata for ndef read * add contributors * update vite * covector setup * tauri/dox removed * docs and examples * fmt [skip ci] --------- Co-authored-by: FabianLars-crabnebula <fabianlars@crabnebula.dev> Co-authored-by: Lucas Nogueira <lucas@tauri.app> Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
co-authored by
FabianLars-crabnebula
Lucas Nogueira
Lucas Nogueira
parent
7c59e3785b
commit
fe79adb5c7
@@ -0,0 +1,272 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { invoke } from "@tauri-apps/api/primitives";
|
||||
|
||||
export const RTD_TEXT = [0x54]; // "T"
|
||||
export const RTD_URI = [0x55]; // "U"
|
||||
|
||||
export interface UriFilter {
|
||||
scheme?: string;
|
||||
host?: string;
|
||||
pathPrefix?: string;
|
||||
}
|
||||
|
||||
export enum TechKind {
|
||||
IsoDep,
|
||||
MifareClassic,
|
||||
MifareUltralight,
|
||||
Ndef,
|
||||
NdefFormatable,
|
||||
NfcA,
|
||||
NfcB,
|
||||
NfcBarcode,
|
||||
NfcF,
|
||||
NfcV,
|
||||
}
|
||||
|
||||
export type ScanKind =
|
||||
| {
|
||||
type: "tag";
|
||||
uri?: UriFilter;
|
||||
mimeType?: string;
|
||||
}
|
||||
| {
|
||||
type: "ndef";
|
||||
uri?: UriFilter;
|
||||
mimeType?: string;
|
||||
/**
|
||||
* Each of the tech-lists is considered independently and the activity is considered a match if
|
||||
* any single tech-list matches the tag that was discovered.
|
||||
* This provides AND and OR semantics for filtering desired techs.
|
||||
*
|
||||
* See <https://developer.android.com/reference/android/nfc/NfcAdapter#ACTION_TECH_DISCOVERED> for more information.
|
||||
*
|
||||
* Examples
|
||||
*
|
||||
* ```ts
|
||||
* import type { TechKind } from "@tauri-apps/plugin-nfc"
|
||||
*
|
||||
* const techLists = [
|
||||
* // capture anything using NfcF
|
||||
* [TechKind.NfcF],
|
||||
* // capture all MIFARE Classics with NDEF payloads
|
||||
* [TechKind.NfcA, TechKind.MifareClassic, TechKind.Ndef]
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
techLists?: TechKind[][];
|
||||
};
|
||||
|
||||
export interface ScanOptions {
|
||||
keepSessionAlive?: boolean;
|
||||
/** Message displayed in the UI. iOS only. */
|
||||
message?: string;
|
||||
/** Message displayed in the UI when the message has been read. iOS only. */
|
||||
successMessage?: string;
|
||||
}
|
||||
|
||||
export interface WriteOptions {
|
||||
kind?: ScanKind;
|
||||
/** Message displayed in the UI when reading the tag. iOS only. */
|
||||
message?: string;
|
||||
/** Message displayed in the UI when the tag has been read. iOS only. */
|
||||
successfulReadMessage?: string;
|
||||
/** Message displayed in the UI when the message has been written. iOS only. */
|
||||
successMessage?: string;
|
||||
}
|
||||
|
||||
export enum NFCTypeNameFormat {
|
||||
Empty = 0,
|
||||
NfcWellKnown = 1,
|
||||
Media = 2,
|
||||
AbsoluteURI = 3,
|
||||
NfcExternal = 4,
|
||||
Unknown = 5,
|
||||
Unchanged = 6,
|
||||
}
|
||||
|
||||
export interface TagRecord {
|
||||
tnf: NFCTypeNameFormat;
|
||||
kind: number[];
|
||||
id: number[];
|
||||
payload: number[];
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: number[];
|
||||
kind: string[];
|
||||
records: TagRecord[];
|
||||
}
|
||||
|
||||
export interface NFCRecord {
|
||||
format: NFCTypeNameFormat;
|
||||
kind: number[];
|
||||
id: number[];
|
||||
payload: number[];
|
||||
}
|
||||
|
||||
export function record(
|
||||
format: NFCTypeNameFormat,
|
||||
kind: string | number[],
|
||||
id: string | number[],
|
||||
payload: string | number[],
|
||||
): NFCRecord {
|
||||
return {
|
||||
format,
|
||||
kind:
|
||||
typeof kind === "string"
|
||||
? Array.from(new TextEncoder().encode(kind))
|
||||
: kind,
|
||||
id: typeof id === "string" ? Array.from(new TextEncoder().encode(id)) : id,
|
||||
payload:
|
||||
typeof payload === "string"
|
||||
? Array.from(new TextEncoder().encode(payload))
|
||||
: payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function textRecord(
|
||||
text: string,
|
||||
id?: string | number[],
|
||||
language: string = "en",
|
||||
): NFCRecord {
|
||||
const payload = Array.from(new TextEncoder().encode(language + text));
|
||||
payload.unshift(language.length);
|
||||
return record(NFCTypeNameFormat.NfcWellKnown, RTD_TEXT, id || [], payload);
|
||||
}
|
||||
|
||||
const protocols = [
|
||||
"",
|
||||
"http://www.",
|
||||
"https://www.",
|
||||
"http://",
|
||||
"https://",
|
||||
"tel:",
|
||||
"mailto:",
|
||||
"ftp://anonymous:anonymous@",
|
||||
"ftp://ftp.",
|
||||
"ftps://",
|
||||
"sftp://",
|
||||
"smb://",
|
||||
"nfs://",
|
||||
"ftp://",
|
||||
"dav://",
|
||||
"news:",
|
||||
"telnet://",
|
||||
"imap:",
|
||||
"rtsp://",
|
||||
"urn:",
|
||||
"pop:",
|
||||
"sip:",
|
||||
"sips:",
|
||||
"tftp:",
|
||||
"btspp://",
|
||||
"btl2cap://",
|
||||
"btgoep://",
|
||||
"tcpobex://",
|
||||
"irdaobex://",
|
||||
"file://",
|
||||
"urn:epc:id:",
|
||||
"urn:epc:tag:",
|
||||
"urn:epc:pat:",
|
||||
"urn:epc:raw:",
|
||||
"urn:epc:",
|
||||
"urn:nfc:",
|
||||
];
|
||||
|
||||
function encodeURI(uri: string): number[] {
|
||||
let prefix = "";
|
||||
|
||||
protocols.slice(1).forEach(function (protocol) {
|
||||
if ((!prefix || prefix === "urn:") && uri.indexOf(protocol) === 0) {
|
||||
prefix = protocol;
|
||||
}
|
||||
});
|
||||
|
||||
if (!prefix) {
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
const encoded = Array.from(
|
||||
new TextEncoder().encode(uri.slice(prefix.length)),
|
||||
);
|
||||
const protocolCode = protocols.indexOf(prefix);
|
||||
// prepend protocol code
|
||||
encoded.unshift(protocolCode);
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
export function uriRecord(uri: string, id?: string | number[]): NFCRecord {
|
||||
return record(
|
||||
NFCTypeNameFormat.NfcWellKnown,
|
||||
RTD_URI,
|
||||
id || [],
|
||||
encodeURI(uri),
|
||||
);
|
||||
}
|
||||
|
||||
function mapScanKind(kind: ScanKind): Record<string, unknown> {
|
||||
const { type: scanKind, ...kindOptions } = kind;
|
||||
return { [scanKind]: kindOptions };
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans an NFC tag.
|
||||
*
|
||||
* ```javascript
|
||||
* import { scan } from "@tauri-apps/plugin-nfc";
|
||||
* await scan({ type: "tag" });
|
||||
* ```
|
||||
*
|
||||
* See <https://developer.android.com/develop/connectivity/nfc/nfc#ndef> for more information.
|
||||
*
|
||||
* @param kind
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
export async function scan(
|
||||
kind: ScanKind,
|
||||
options?: ScanOptions,
|
||||
): Promise<Tag> {
|
||||
return await invoke("plugin:nfc|scan", {
|
||||
kind: mapScanKind(kind),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to an NFC tag.
|
||||
*
|
||||
* ```javascript
|
||||
* import { uriRecord, write } from "@tauri-apps/plugin-nfc";
|
||||
* await write([uriRecord("https://tauri.app")], { kind: { type: "ndef" } });
|
||||
* ```
|
||||
*
|
||||
* If you did not previously call {@link scan} with {@link ScanOptions.keepSessionAlive} set to true,
|
||||
* it will first scan the tag then write to it.
|
||||
*
|
||||
* @param records
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
export async function write(
|
||||
records: NFCRecord[],
|
||||
options?: WriteOptions,
|
||||
): Promise<void> {
|
||||
const { kind, ...opts } = options || {};
|
||||
if (kind) {
|
||||
// @ts-expect-error map the property
|
||||
opts.kind = mapScanKind(kind);
|
||||
}
|
||||
return await invoke("plugin:nfc|write", {
|
||||
records,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
export async function isAvailable(): Promise<boolean> {
|
||||
return await invoke("plugin:nfc|isAvailable");
|
||||
}
|
||||
Reference in New Issue
Block a user