أنشئ APK مجانًا

واجهة JavaScript bridge API

NFC

قراءة وسم عبر window.AndroidNFC، وجانب الكتابة خلف polyfill الخاص بـ NDEFReader.

NDEFReader معيار الويب#

new NDEFReader() — Web NFC

مثال

Web NFC القياسي: يقرأ new NDEFReader() وسوم NFC ويكتب عليها ويقفلها داخل التطبيق المثبّت، بالشيفرة نفسها التي يقبلها Chrome على Android.

القيمة المُعادة: وعود وأحداث reading / readingerror، كما في معيار الويب. السجلات هي NDEFRecord: recordType ('text' أو 'url' أو 'absolute-url' أو 'mime' أو 'smart-poster' أو 'empty' أو 'unknown' أو نوع خارجي مثل 'example.com:thing')، وmediaType (سجلات mime)، وid وencoding وlang (سجلات النص) وdata كـ DataView. المتطلبات: فعّل Web NFC Support في الخطوة 4 (Access) عند الإنشاء. بدونه لا يوجد NDEFReader. لا نافذة إذن وقت التشغيل؛ ويجيب navigator.permissions.query({name:'nfc'}) granted.

اقرأ الوسوم. فكّ ترميز data بـ TextDecoder، تمامًا كما في Chrome:

function describe(record) {
  if (record.recordType === 'text') return new TextDecoder(record.encoding).decode(record.data);
  if (record.recordType === 'url' || record.recordType === 'absolute-url') return new TextDecoder().decode(record.data);
  if (record.recordType === 'mime') return record.mediaType + ' (' + record.data.byteLength + ' bytes)';
  return record.recordType;
}

var scanStop = null;

document.getElementById('read').addEventListener('click', async function () {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  var reader = new NDEFReader();
  scanStop = new AbortController();
  reader.onreading = function (e) {
    showStatus('Tag ' + e.serialNumber + ': ' + e.message.records.map(describe).join(' | '));
  };
  reader.onreadingerror = function () { showStatus('That tag holds no NFC data this phone can read.'); };
  try {
    await reader.scan({ signal: scanStop.signal });
    showStatus('Hold a tag to the back of the phone.');
  } catch (err) {
    showStatus('NFC error: ' + err.name + ' ' + err.message);   // NotReadableError = NFC switched off
  }
});

document.getElementById('stop').addEventListener('click', function () {
  if (scanStop) scanStop.abort();   // the standard way to stop scanning
});

اكتب نصًا أو رابطًا. يُحَلّ الـ Promise بعد أن ينقر المستخدم وسمًا:

async function writeTag(kind, value) {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  var reader = new NDEFReader();
  showStatus('Tap a tag to write it...');
  try {
    if (kind === 'text') await reader.write(value);   // same as { records: [{ recordType: 'text', data: value }] }
    else await reader.write({ records: [{ recordType: 'url', data: value }] }, { overwrite: false });
    showStatus('Tag written.');
  } catch (err) {
    showStatus('Write failed: ' + err.name + ' ' + err.message);   // NotAllowedError: read-only, or overwrite:false on a used tag
  }
}

اقفل وسمًا إلى الأبد. لا يمكن التراجع عن makeReadOnly() - اسأل أولًا:

async function lockTag() {
  if (!('NDEFReader' in window)) return;
  if (!confirm('Lock the next tag you tap? It can never be changed again.')) return;
  try { await new NDEFReader().makeReadOnly(); showStatus('Tag locked.'); }
  catch (err) { showStatus('Lock failed: ' + err.message); }
}

ملاحظات: أنواع السجلات لـ write: text (نص، أو بايتات مع encoding)، وurl وabsolute-url (نص)، وmime (بايتات + mediaType)، وsmart-poster ({records: [...]})، ونوع خارجي 'domain:thing' (بايتات أو {records: [...]})، وunknown (بايتات) وempty. النص العادي يكتب سجل نص؛ والبايتات العادية تكتب سجل mime من نوع application/octet-stream. فقط القرّاء الذين استدعوا scan() يتلقون أحداث reading؛ وAbortController.abort() على signal يوقف ذلك القارئ (لا يوجد stop() في المعيار). يُطلَق readingerror لوسم لا يستطيع حمل بيانات NFC. ينتظر الهاتف عملية وسم واحدة في كل مرة: write() أو makeReadOnly() جديد أثناء وجود انتظار يستبدله، ويُرفض الاستدعاء المستبدَل بـ AbortError (بينما ينتظره Chrome في طابور). وsignal على write() / makeReadOnly() يلغي الانتظار. الأخطاء: NotSupportedError (لا NFC، أو الوسم لا يحمل NDEF، أو صغير جدًا)، NotReadableError (NFC متوقف)، NotAllowedError (وسم للقراءة فقط، أو overwrite: false على وسم يحتوي سجلات)، NetworkError (ابتعد الوسم)، TypeError (سجل لا يسمح به المعيار). NDEFReader موجود في الصفحة الرئيسية، وليس داخل iframes.

__nfcCancelWrite الجسر (bridge)#

window.WebToApk.__nfcCancelWrite()

يلغي عملية الكتابة/القفل المنتظرة (يستدعيه الـ polyfill عندما يُلغى `signal` الخاص بالعملية).

مثال

يُسقط كتابة أو قفل NFC في الانتظار حتى تُقرأ النقرة التالية على الوسم بشكل عادي. تلغي الصفحات عبر معيار الويب - AbortController signal - ويستدعي الـ polyfill الخاص بـ NDEFReader هذا نيابةً عنها.

القيمة المُعادة: لا شيء. المتطلبات: تفعيل Web NFC Support في الخطوة 4 (Access) عند الإنشاء.

الطريقة القياسية، التي يحترمها التطبيق - يُرفض write() المنتظر بـ AbortError:

var controller = null;

async function writeWithCancel(text) {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  controller = new AbortController();
  showStatus('Tap a tag to write it... (or press Cancel)');
  try {
    await new NDEFReader().write(text, { signal: controller.signal });
    showStatus('Tag written.');
  } catch (err) {
    showStatus(err.name === 'AbortError' ? 'Cancelled.' : 'Write failed: ' + err.message);
  }
}

document.getElementById('cancel').addEventListener('click', function () {
  if (controller) controller.abort();
});

يستسلم من تلقاء نفسه عندما لا يصل وسم في الوقت المناسب؛ ومؤقّت يُلغي الـ signal يلغي بالطريقة نفسها:

async function writeWithTimeout(url, ms) {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  var controller = new AbortController();
  var timer = setTimeout(function () { controller.abort(); }, ms);
  showStatus('Tap a tag within ' + Math.round(ms / 1000) + ' seconds...');
  try {
    await new NDEFReader().write({ records: [{ recordType: 'url', data: url }] }, { signal: controller.signal });
    showStatus('Tag written.');
  } catch (err) {
    showStatus(err.name === 'AbortError' ? 'Timed out. Tap Write to try again.' : 'Write failed: ' + err.message);
  } finally {
    clearTimeout(timer);
  }
}

ألغِ قفلًا (أو كتابة) منتظرة عندما يغادر المستخدم الشاشة، حتى لا تغيّر نقرة لاحقة وسمًا:

var pendingTagOp = null;

window.addEventListener('hashchange', function () {
  if (pendingTagOp) { pendingTagOp.abort(); pendingTagOp = null; }
});

document.getElementById('lock').addEventListener('click', function () {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  if (pendingTagOp) pendingTagOp.abort();
  var controller = pendingTagOp = new AbortController();
  showStatus('Tap the tag to lock it...');
  new NDEFReader().makeReadOnly({ signal: controller.signal })
    .then(function () { showStatus('Tag locked.'); })
    .catch(function (err) { if (err.name !== 'AbortError') showStatus('Lock failed: ' + err.message); })
    .finally(function () { if (pendingTagOp === controller) pendingTagOp = null; });
});

ملاحظات: لا تستدعِ WebToApk.__nfcCancelWrite() بنفسك: عند استدعائه مباشرة يُسقط الانتظار الأصلي لكنه لا يستطيع إنهاء Promise الصفحة. أما signal فيفعل الاثنين. كما أن الإلغاء يُسقط أيضًا ما هو في الانتظار makeReadOnly().

__nfcMakeReadOnly الجسر (bridge)#

window.WebToApk.__nfcMakeReadOnly(): String

يضع في الانتظار قفلًا دائمًا (`NDEFReader.makeReadOnly()`) للنقرة التالية على الوسم؛ ويعيد معرّف عمليته، أو "" عندما يكون NFC متوقفًا في هذا التطبيق.

مثال

بنية نقل داخلية خلف NDEFReader.makeReadOnly(). تستخدم الصفحات new NDEFReader().makeReadOnly()؛ لا تستدعِ هذا مباشرة.

القيمة المُعادة: (عبر makeReadOnly) وعد (Promise) يُحَلّ بعد أن ينقر المستخدم وسمًا ويُقفَل، أو يُرفض بـ DOMException. المتطلبات: تفعيل Web NFC Support في الخطوة 4 (Access) عند الإنشاء.

القفل دائم. اكتب الوسم أولًا ثم اقفله، واسأل المستخدم قبل الأمرين:

document.getElementById('publish-tag').addEventListener('click', async function () {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  if (!confirm('Write and lock this tag? A locked tag can never be changed.')) return;
  var reader = new NDEFReader();
  try {
    showStatus('Tap the tag to write it...');
    await reader.write({ records: [{ recordType: 'url', data: 'https://example.com/item/42' }] });
    showStatus('Now tap the same tag again to lock it...');
    await reader.makeReadOnly();
    showStatus('Tag written and locked.');
  } catch (err) {
    showStatus('Failed: ' + err.message);
  }
});

اقفل وسمًا مكتوبًا بالفعل، واستسلم إن لم يصل وسم خلال 20 ثانية:

async function lockTag() {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  if (!confirm('Lock this tag for good? It can never be changed again.')) return;
  var controller = new AbortController();
  var timer = setTimeout(function () { controller.abort(); }, 20000);
  showStatus('Tap the tag to lock it...');
  try {
    await new NDEFReader().makeReadOnly({ signal: controller.signal });
    showStatus('Tag locked.');
  } catch (err) {
    if (err.name === 'AbortError') showStatus('No tag was tapped. Try again when ready.');
    else if (err.name === 'NotAllowedError') showStatus('This kind of tag cannot be locked.');
    else showStatus('Lock failed: ' + err.message);
  } finally {
    clearTimeout(timer);
  }
}

اقرأ وسمًا أولًا ولا تعرض قفله إلا عندما يحتوي الرابط المتوقع:

async function checkThenLock(expectedUrl) {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  var reader = new NDEFReader();
  var scanning = new AbortController();
  var lockBtn = document.getElementById('lock-btn');
  reader.onreading = function (e) {
    var rec = e.message.records.find(function (r) { return r.recordType === 'url'; });
    var url = rec ? new TextDecoder().decode(rec.data) : null;
    showStatus(url ? 'Tag points to ' + url : 'This tag holds no link.');
    lockBtn.disabled = url !== expectedUrl;
  };
  await reader.scan({ signal: scanning.signal });
  showStatus('Hold the tag to the phone to check it.');

  lockBtn.onclick = async function () {
    if (!confirm('Lock this tag? It can never be changed again.')) return;
    scanning.abort();
    showStatus('Tap the tag again to lock it...');
    try { await reader.makeReadOnly(); showStatus('Tag locked.'); }
    catch (err) { showStatus('Lock failed: ' + err.message); }
  };
}

ملاحظات: كل خطوة تحتاج نقرتها الخاصة. يعيد الاستدعاء معرّف عملية وتصل النتيجة عبر window.__appmintNfcDone. حالات الرفض: NotSupportedError "Tag does not support NDEF"، NotAllowedError "This tag cannot be made read-only" (كثير من الوسوم لا يمكن قفلها)، NetworkError عندما يبتعد الوسم. بدء write() أثناء انتظار قفل يستبدل القفل، فيُرفض بـ AbortError. وsignal الممرَّر إلى makeReadOnly({ signal }) يلغي الانتظار.

__nfcState الجسر (bridge)#

window.WebToApk.__nfcState(): String

"on" أو "off" (NFC متوقف في إعدادات الهاتف) أو "none" (لا يوجد NFC، أو غير مفعّل في هذا التطبيق) - وهو أول ما يفحصه `NDEFReader.scan()`.

مثال

فحص داخلي خلف NDEFReader.scan(): هل يستطيع هذا التطبيق استخدام NFC الآن. تستدعي الصفحات scan() وتقرأ رفضه بدلًا من ذلك.

القيمة المُعادة: 'on' أو 'off' (NFC متوقف في إعدادات الهاتف) أو 'none' (لا يوجد NFC في الهاتف، أو بُني التطبيق بدون Web NFC Support). المتطلبات: تفعيل Web NFC Support في الخطوة 4 (Access) عند الإنشاء.

الطريقة القياسية - يُرفض scan() بالإجابة نفسها:

async function startReading() {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  try {
    await new NDEFReader().scan();
    showStatus('Hold a tag to the back of the phone.');
  } catch (err) {
    if (err.name === 'NotReadableError') showStatus('Turn on NFC in the phone settings, then try again.');
    else if (err.name === 'NotSupportedError') showStatus('This phone has no NFC.');
    else showStatus('NFC error: ' + err.message);
  }
}

افحص مرة واحدة عند فتح الشاشة، لتختار بين لوحة NFC وتلميح "turn on NFC" وإخفاء NFC كليًا. إلغاء مسح الفحص فورًا لا يترك شيئًا يعمل:

async function nfcStatus() {
  if (!('NDEFReader' in window)) return 'none';
  var probe = new AbortController();
  try {
    await new NDEFReader().scan({ signal: probe.signal });
    return 'on';
  } catch (err) {
    return err.name === 'NotReadableError' ? 'off' : 'none';
  } finally {
    probe.abort();
  }
}

async function refreshNfcUi() {
  var state = await nfcStatus();
  document.getElementById('nfc-panel').hidden = state !== 'on';
  document.getElementById('nfc-off-hint').hidden = state !== 'off';
}

refreshNfcUi();
document.addEventListener('visibilitychange', function () {
  if (document.visibilityState === 'visible') refreshNfcUi(); // back from the phone settings
});

اقرأ الوسوم فقط أثناء فتح شاشة واحدة، وميّز بين وسم فارغ أو غير مقروء وبين كون NFC متوقفًا:

var tagScreen = null;

async function openTagScreen() {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  tagScreen = new AbortController();
  var reader = new NDEFReader();
  reader.onreading = function (e) {
    var n = e.message.records.length;
    showStatus(n ? 'Tag ' + e.serialNumber + ' holds ' + n + ' record(s).' : 'This tag is empty.');
  };
  reader.onreadingerror = function () { showStatus('This tag cannot be read. Try another one.'); };
  try {
    await reader.scan({ signal: tagScreen.signal });
    showStatus('Hold a tag to the back of the phone.');
  } catch (err) {
    showStatus(err.name === 'NotReadableError' ? 'NFC is off. Turn it on in the phone settings.' : 'NFC error: ' + err.message);
  }
}

function closeTagScreen() {
  if (tagScreen) { tagScreen.abort(); tagScreen = null; }
}

ملاحظات: هذه بنية داخلية للـ polyfill؛ استخدم scan(). يجيب window.AndroidNFC.startScan() عن السؤال نفسه بقيمة منطقية ويعرض رسالة "Please enable NFC" عندما يكون NFC متوقفًا.

__nfcWrite الجسر (bridge)#

window.WebToApk.__nfcWrite(messageJson: String): String

يضع [messageJson] (`{records:[…], overwrite}`، الذي يبنيه `NDEFReader.write()`) في الانتظار للنقرة التالية على وسم ويعيد معرّف عمليته؛ و"" عندما لا يكون NFC مفعّلًا في هذا التطبيق.

مثال

بنية نقل داخلية خلف NDEFReader.write(message). تستخدم الصفحات new NDEFReader().write(...)؛ لا تستدعِ هذا مباشرة.

القيمة المُعادة: (عبر write) وعد (Promise) يُحَلّ بعد أن ينقر المستخدم وسمًا ويُكتَب عليه، أو يُرفض بـ DOMException. المتطلبات: تفعيل Web NFC Support في الخطوة 4 (Access) عند الإنشاء.

document.getElementById('write-table-tag').addEventListener('click', async function () {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  showStatus('Tap a tag to write it...');
  try {
    await new NDEFReader().write({
      records: [
        { recordType: 'url', data: 'https://example.com/menu' },
        { recordType: 'text', data: 'Table 12', lang: 'en' },
        { recordType: 'mime', mediaType: 'application/json',
          data: new TextEncoder().encode(JSON.stringify({ table: 12 })) }
      ]
    });
    showStatus('Tag written.');
  } catch (err) {
    showStatus('Write failed: ' + err.message);
  }
});

اكتب على وسم فارغ فقط، حتى لا يُستبدل وسم يحتوي بيانات بالخطأ:

async function writeToBlankTag(url) {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  showStatus('Tap a blank tag...');
  try {
    await new NDEFReader().write(
      { records: [{ recordType: 'url', data: url }] },
      { overwrite: false }
    );
    showStatus('Tag written.');
  } catch (err) {
    if (err.name === 'NotAllowedError') showStatus('That tag already holds data or is locked. Use a blank tag.');
    else if (err.name === 'NetworkError') showStatus('Hold the tag still until it is written.');
    else showStatus('Write failed: ' + err.message);
  }
}

اكتب smart poster، أي رابطًا مع عنوانه، كسجل واحد:

document.getElementById('write-poster').addEventListener('click', async function () {
  if (!('NDEFReader' in window)) { showStatus('NFC is not available in this app.'); return; }
  showStatus('Tap a tag to write the poster...');
  try {
    await new NDEFReader().write({
      records: [{
        recordType: 'smart-poster',
        data: { records: [
          { recordType: 'url', data: 'https://example.com/events/spring-fair' },
          { recordType: 'text', data: 'Spring Fair tickets', lang: 'en' }
        ] }
      }]
    });
    showStatus('Poster tag written.');
  } catch (err) {
    showStatus(err.name === 'NotSupportedError' ? 'This tag is too small or cannot hold NFC data.' : 'Write failed: ' + err.message);
  }
});

ملاحظات: يفحص الـ polyfill السجلات ويحوّلها إلى {records:[{recordType, id, data(base64), encoding, lang, mediaType, records?}], overwrite} ويمرّر نص JSON إلى هنا. يعيد الاستدعاء معرّف عملية؛ وتعود النتيجة إلى الصفحة عبر window.__appmintNfcDone(id, errorName, message)، فلا تُنهي إجابةُ كتابة مُستبدَلة كتابةً أحدث. الوسوم غير المهيّأة تُهيَّأ أولًا. حالات الرفض: NotAllowedError ("Tag is read-only"، أو وسم يحتوي سجلات عندما تكون overwrite false)، NotSupportedError ("Tag does not support NDEF"، "Tag too small (N bytes available)")، NetworkError (ابتعد الوسم)، SyntaxError (سجل لا يستوعبه تنسيق الوسم). كما يعرض التطبيق رسالتيه القصيرتين "Ready to write" و"NFC tag written".

startScan الجسر (bridge)#

window.AndroidNFC.startScan(): Boolean

فحص توفّر قديم، موجود فقط عندما فعّل المنشئ NFC. عمل NFC الفعلي يمر عبر polyfill القياسي NDEFReader (window.WebToApk.__nfc*)، وهو ما ينبغي أن تستخدمه الصفحة؛ وهذا يخبر فقط إن كان المحوّل يعمل.

موصوف عبر مجموعته، Web NFC، وليس بمفرده.

مثال

يخبرك إن كان NFC في الهاتف يعمل. وهو موجود في window.AndroidNFC وليس في window.WebToApk، ولا يقرأ وسمًا - فالقراءة تتم بـ NDEFReader.

القيمة المُعادة: true (بشكل متزامن) عندما يحتوي الهاتف على NFC وهو يعمل؛ وfalse عندما لا يحتوي الهاتف على NFC أو يكون NFC متوقفًا (فيعرض التطبيق أيضًا رسالة قصيرة "Please enable NFC"). المتطلبات: تفعيل Web NFC Support في الخطوة 4 (Access) عند الإنشاء؛ وبدونه لا يوجد window.AndroidNFC.

افحص قبل عرض زر NFC:

function nfcReady() {
  if (!window.AndroidNFC || typeof window.AndroidNFC.startScan !== 'function') return false;
  return window.AndroidNFC.startScan();
}

document.getElementById('scan-tag').addEventListener('click', async function () {
  if (!nfcReady()) { showStatus('Turn on NFC in the phone settings, then try again.'); return; }
  var reader = new NDEFReader();
  reader.addEventListener('reading', function (e) { showStatus('Tag ' + e.serialNumber); });
  await reader.scan();
  showStatus('Hold a tag to the back of the phone.');
});

افحص قبل الكتابة مباشرة: مع إيقاف NFC ستنتظر الكتابة نقرة لا تأتي أبدًا، بينما يجيب startScan() فورًا ويعرض رسالة التطبيق "Please enable NFC":

document.getElementById('write-pass').addEventListener('click', async function () {
  if (!window.AndroidNFC || typeof window.AndroidNFC.startScan !== 'function') {
    showStatus('NFC is not available in this app.');
    return;
  }
  if (!window.AndroidNFC.startScan()) { showStatus('NFC is off, or this phone has no NFC.'); return; }
  showStatus('Tap a tag to write it...');
  try {
    await new NDEFReader().write('Visitor pass #1042');
    showStatus('Tag written.');
  } catch (err) {
    showStatus('Write failed: ' + err.message);
  }
});

أعد الفحص عندما يعود المستخدم وينقر "I've turned it on":

var retry = document.getElementById('nfc-retry');
retry.addEventListener('click', function () {
  if (!window.AndroidNFC || typeof window.AndroidNFC.startScan !== 'function') {
    showStatus('NFC is not available in this app.');
    return;
  }
  if (window.AndroidNFC.startScan()) {
    retry.hidden = true;
    document.getElementById('nfc-panel').hidden = false;
    startReading(); // your own NDEFReader.scan() code
  } else {
    showStatus('NFC is still off, or this phone has no NFC.');
  }
});

ملاحظات: window.AndroidNFC موجود منذ أول سكربت في الصفحة. بعد أن يشغّل المستخدم NFC من الإعدادات ويعود، تبدأ قراءة الوسوم من تلقاء نفسها.

يتم إنشاؤه من بيئة تشغيل التطبيق وملفات الأمثلة الخاصة به عند كل بناء للمستندات. اقرأه بصيغة Markdown · جميع العائلات.

تمت المراجعة مقابل الجسر المُرفق بتاريخ 2026-10-22.