Files
tauri-plugins-workspace/plugins/nfc/guest-js/index.ts
T
Lucas Nogueira fe79adb5c7 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>
2023-12-19 10:50:31 -03:00

273 lines
5.9 KiB
TypeScript

// 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");
}