أنشئ APK مجانًا

واجهة JavaScript bridge API

صندوق push والجمهور

رسائل أرسلها المنشئ من لوحة تحكم، محفوظة على الجهاز كي تعرض الصفحة سجلًا بدل نافذة منبثقة في شريط الإشعارات فقط - مع معرّف المستخدم المسجّل الدخول ووسومه التي تتيح لمركز الإرسال استهداف مستخدم واحد أو وسم، وتشغيل أتمتة مثل تذكير السلة المتروكة.

AppMint.trackEvent دالة مساعدة (helper)#

window.AppMint.trackEvent

مثال

موثَّق مع WebToApk.trackEvent - والمثال هناك يعرض هذا أيضًا.

يُخبر التطبيق بأن شيئًا حدث ("signed_up"، "checkout_done"، "level:3"). تظهر الآن رسالة داخل التطبيق أنشأها المنشئ في مركز إرسال AppMint Push بمشغّل "عند حدوث حدث" وبالاسم نفسه (أو بعد مدة الانتظار التي اختارها المنشئ).

القيمة المُعادة: true عندما قُبل الحدث؛ false لاسم خارج الحروف والأرقام و_ . : - (حتى 64)، وفي تطبيق بُني بدون AppMint Push. AppMint.trackEvent(name, props) هو الاستدعاء نفسه مع props ككائن. المتطلبات: تفعيل AppMint Push في خطوة Integrate (الخطوة 3) عند الإنشاء. propsJson اختياري ('') ولا يُخزَّن.

// After a purchase: the creator's "Thanks, rate us?" message is set to trigger on "purchase_done".
function onPurchaseDone(order) {
  if (window.AppMint && AppMint.trackEvent) {
    AppMint.trackEvent('purchase_done', { total: order.total });
  }
}

تأخذ الجسر الخام الخصائص كسلسلة JSON (مرّر '' إن لم تكن هناك خصائص):

function track(name, props) {
  if (!(window.WebToApk && window.WebToApk.trackEvent)) return false;   // a browser: nothing to show
  return WebToApk.trackEvent(name, props ? JSON.stringify(props) : '');
}
track('level:3');
track('tutorial_skipped', { step: 2 });

يمكن الإبلاغ عن تغيّر الشاشة الذي لا يظهر في العنوان (تبويبات، معالج، "صفحة" نافذة منبثقة) كمسار، فتراه رسائل المنشئ "في صفحة". ويمكن للصفحة أيضًا إرسال الحدث appmint:route مع المسار؛ والتطبيق يستمع إليه:

function showTab(name) {
  document.querySelectorAll('.tab').forEach(function (t) { t.hidden = t.id !== name; });
  if (window.AppMint && AppMint.trackEvent) AppMint.trackEvent('appmint:route', { path: '/' + name });
}

// Same thing, without touching the bridge at all:
window.dispatchEvent(new CustomEvent('appmint:route', { detail: { path: '/cart' } }));

ملاحظات: رسائل المسارات تتبع بالفعل تغيّرات الروابط الحقيقية (تحميل الصفحات، مسارات history.pushState و#/hash) من تلقاء نفسها؛ بلّغ عن مسار فقط للعروض التي لا يتغيّر فيها العنوان. الأحداث لا تُحسب ولا تُخزَّن في أي مكان؛ هي فقط تحدد هل تظهر رسالة.

clearPushInbox الجسر (bridge)#

window.WebToApk.clearPushInbox(): Boolean

يُفرغ صندوق رسائل الدفع (شريط الإشعارات وسجل الخادم لا يتأثران).

مثال

يُفرغ صندوق رسائل الدفع على هذا الهاتف.

تُرجع: true عند الانتهاء؛ false عند خطأ داخلي. المتطلبات: فعّل AppMint Push في خطوة التكامل (الخطوة 3) عند الإنشاء.

document.getElementById('clear-inbox').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.clearPushInbox)) return;
  if (!confirm('Delete all messages? This cannot be undone.')) return;
  if (WebToApk.clearPushInbox()) renderInbox();
});

أفرغ الصندوق عندما يسجّل المستخدم الخروج، كي لا يرى الشخص التالي على هذا الهاتف رسائله:

async function signOut() {
  await myAuth.signOut();
  if (window.WebToApk && window.WebToApk.clearPushInbox) {
    WebToApk.clearPushInbox();
  }
  document.getElementById('bell-count').textContent = '';
  showLoginScreen();
}

اعرض صفًا في الإعدادات بعدد الرسائل المخزنة، معطّلًا عندما لا يوجد ما يُحذف:

function paintClearRow() {
  const btn = document.getElementById('clear-inbox');
  if (!(window.WebToApk && window.WebToApk.clearPushInbox)) { btn.hidden = true; return; }
  const count = JSON.parse(WebToApk.getPushInbox() || '[]').length;
  btn.textContent = count ? 'Delete ' + count + ' messages' : 'No messages';
  btn.disabled = count === 0;
}

document.getElementById('clear-inbox').addEventListener('click', function () {
  if (WebToApk.clearPushInbox()) paintClearRow();
  else alert('Could not delete the messages. Please try again.');
});

paintClearRow();

ملاحظات: هذا دائم - وعلى عكس dismissPushMessage، لا يوجد تراجع. إشعارات الشريط وسجل الإرسال في AppMint لا تُمَس. الرسائل الجديدة تظل تصل.

dismissPushMessage الجسر (bridge)#

window.WebToApk.dismissPushMessage(id: String): Boolean

يُخفي رسالة واحدة من الصندوق - التمرير جانبًا لكل صف الذي تحتاجه شاشة الصندوق (clearPushInbox إما الكل أو لا شيء). قابل للتراجع بالتصميم: الإدخال مخفي لا ممحو، فيمكن للواجهة عرض "تراجع" لبضع ثوانٍ بعد التمرير. محلي فقط: إشعار شريط الإشعارات وسجل المنشئ لا يتأثران.

مثال

يُخفي رسالة واحدة من صندوق رسائل الدفع (بالتمرير جانبًا). يمكن التراجع عن ذلك باستخدام restorePushMessage(id).

القيمة المُعادة: true إن أُخفيت الرسالة؛ false إن كان المعرّف غير معروف أو أُخفيت بالفعل. المتطلبات: فعّل AppMint Push في خطوة Integrate (الخطوة 3) عند الإنشاء.

التمرير جانبًا مع تراجع لمدة 5 ثوانٍ:

function dismissRow(id, rowEl) {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return;
  if (!WebToApk.dismissPushMessage(id)) return;
  rowEl.remove();

  const bar = document.getElementById('undo-bar');
  bar.hidden = false;
  const timer = setTimeout(function () { bar.hidden = true; }, 5000);
  document.getElementById('undo-btn').onclick = function () {
    clearTimeout(timer);
    bar.hidden = true;
    WebToApk.restorePushMessage(id);         // back in its old place
    renderInbox();
  };
}

رتّب الصندوق عند فتحه بإخفاء الرسائل المقروءة والأقدم من 30 يومًا:

function hideOldReadMessages() {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return 0;
  const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
  let hidden = 0;
  JSON.parse(WebToApk.getPushInbox() || '[]').forEach(function (m) {
    if (m.read && m.receivedAt < cutoff && WebToApk.dismissPushMessage(m.id)) hidden++;
  });
  return hidden;
}

if (hideOldReadMessages() > 0) renderInbox();

أخفِ صفًا بتمرير حقيقي بالإصبع (اسحبه أكثر من 40% من عرضه):

function makeSwipeable(rowEl, id) {
  let startX = null;
  rowEl.addEventListener('pointerdown', function (e) { startX = e.clientX; rowEl.setPointerCapture(e.pointerId); });
  rowEl.addEventListener('pointermove', function (e) {
    if (startX !== null) rowEl.style.transform = 'translateX(' + (e.clientX - startX) + 'px)';
  });
  rowEl.addEventListener('pointerup', function (e) {
    const dx = startX === null ? 0 : e.clientX - startX;
    startX = null;
    if (Math.abs(dx) > rowEl.offsetWidth * 0.4 && window.WebToApk && WebToApk.dismissPushMessage(id)) {
      rowEl.remove();
    } else {
      rowEl.style.transform = '';            // not far enough: snap back
    }
  });
}

ملاحظات: يتغيّر صندوق هذا الهاتف فقط. يبقى إشعار شريط الإشعارات وسجل إرسالك في AppMint. لا تُرجع الدالة الرسائل المخفية getPushInbox().

getInAppCampaigns الجسر (bridge)#

window.WebToApk.getInAppCampaigns(): String

الرسائل داخل التطبيق التي يحتفظ بها هذا التثبيت: JSON [{id, name}] ("[]" بدون AppMint Push).

مثال

يسرد الرسائل داخل التطبيق التي يحملها هذا التثبيت الآن (حملات المنشئ من مركز إرسال AppMint Push التي يكون هذا المستخدم ضمن جمهورها) كنص JSON [{"id": "...", "name": "..."}].

القيمة المُعادة: نص مصفوفة JSON؛ "[]" عندما لا يوجد شيء وفي تطبيق بُني بدون AppMint Push. المتطلبات: تفعيل AppMint Push في خطوة Integrate (الخطوة 3) عند الإنشاء. تُحدَّث القائمة عند كل بدء للتطبيق (وكل ساعة تقريبًا في المقدمة).

var list = [];
if (window.WebToApk && window.WebToApk.getInAppCampaigns) {
  try { list = JSON.parse(WebToApk.getInAppCampaigns()); } catch (e) {}
}
console.log('in-app messages on this phone:', list.map(function (c) { return c.name; }));

اعرض قائمة تصحيح في شاشة الإدارة الخاصة بك:

function renderInAppDebug(el) {
  var raw = (window.WebToApk && WebToApk.getInAppCampaigns) ? WebToApk.getInAppCampaigns() : '[]';
  var items = JSON.parse(raw);
  el.innerHTML = items.length
    ? items.map(function (c) { return '<li>' + c.name + ' <code>' + c.id + '</code></li>'; }).join('')
    : '<li>No in-app messages for this install</li>';
}
renderInAppDebug(document.getElementById('inapp-debug'));

تجاوز تلميح الترحيب الخاص بك عندما يشغّل المنشئ بالفعل رسالة بهذا الاسم:

function hasCampaign(name) {
  try { return JSON.parse(WebToApk.getInAppCampaigns()).some(function (c) { return c.name === name; }); }
  catch (e) { return false; }
}
if (!hasCampaign('Welcome tour')) showMyOwnWelcome();

ملاحظات: يُكشَف المعرّف والاسم فقط؛ وتصل الرسالة نفسها إلى الصفحة عبر حدث appmint:inapp عند ظهورها (انظر setInAppPresentation).

getPushInbox الجسر (bridge)#

window.WebToApk.getPushInbox(): String

صندوق AppMint Push (رسائل أرسلها المنشئ، الأحدث أولًا، بحد أقصى 50): مصفوفة JSON من {id, title, body, imageUrl, link, receivedAt, read}. دائمًا الحقيقة الحالية الكاملة - فالتطبيق الذي يُحمَّل متأخرًا لا يخسر شيئًا لفواته أحداث `appmint:push` المباشرة.

مثال

يقرأ صندوق الرسائل داخل التطبيق لرسائل الدفع التي أرسلتها بـ AppMint Push: الأحدث أولًا، حتى 50، دون التي أخفاها المستخدم.

القيمة المُعادة: نص JSON، بشكل متزامن. حلّله. كل عنصر: { id, title, body, imageUrl, link, receivedAt, read } (receivedAt بـ epoch ms؛ وimageUrl وlink هما '' عندما لا تملكهما الرسالة)، إضافة إلى حقول تصميم اختيارية عندما استخدمت قالبًا في AppMint: template وaccent وiconUrl وbuttons: [{label, link}] وbadge وinapp وcountdownAt وprogress وlines وcollapse. '[]' عندما يكون فارغًا. المتطلبات: فعّل AppMint Push في خطوة Integrate (الخطوة 3) عند الإنشاء؛ وبدونه يكون الصندوق فارغًا دائمًا.

اعرض الصندوق، وحدّثه مباشرة بـ appmint:push:

function loadInbox() {
  if (!(window.WebToApk && window.WebToApk.getPushInbox)) return [];
  try { return JSON.parse(WebToApk.getPushInbox() || '[]'); } catch (e) { return []; }
}

function renderInbox() {
  const inbox = loadInbox();
  const unread = inbox.filter(function (m) { return !m.read; }).length;
  document.getElementById('bell-count').textContent = unread ? String(unread) : '';
  const ul = document.getElementById('inbox');
  ul.innerHTML = '';
  inbox.forEach(function (m) {
    const li = document.createElement('li');
    li.className = m.read ? 'read' : 'unread';
    li.textContent = m.title + ' — ' + m.body;
    li.onclick = function () {
      WebToApk.markPushRead(m.id);
      if (m.link) location.href = m.link;
      renderInbox();
    };
    ul.appendChild(li);
  });
}

// While the app is open:
window.addEventListener('appmint:push', function (e) {
  // { type: 'received', message: {id, title, body, ...}, presented: true|false }
  // { type: 'revoked', id }  — you retracted the message
  // presented = the app already showed it on screen (see setPushPresentation)
  if (e.detail.type === 'received' && !e.detail.presented) showBanner(e.detail.message.title);
  renderInbox();
});

renderInbox();

حدّث عدد غير المقروء على جرسك كلما عاد المستخدم إلى التطبيق (الرسائل التي وصلت أثناء وجوده في الخلفية موجودة بالفعل في الصندوق):

function unreadCount() {
  if (!(window.WebToApk && window.WebToApk.getPushInbox)) return 0;
  try {
    return JSON.parse(WebToApk.getPushInbox() || '[]').filter(function (m) { return !m.read; }).length;
  } catch (e) { return 0; }
}

function paintBell() {
  const n = unreadCount();
  const badge = document.getElementById('bell-count');
  badge.textContent = n > 9 ? '9+' : (n ? String(n) : '');
  badge.hidden = n === 0;
}

document.addEventListener('visibilitychange', function () {
  if (document.visibilityState === 'visible') paintBell();
});
paintBell();

اعرض صورة الرسالة وأزرارها (فقط إذا صمّمتها بها)، مع وقت وصولها:

function renderCard(m) {
  const card = document.createElement('article');
  if (m.accent) card.style.borderLeft = '4px solid ' + m.accent;
  if (m.imageUrl) {
    const img = document.createElement('img');
    img.src = m.imageUrl; img.alt = '';
    card.appendChild(img);
  }
  const h = document.createElement('h3');
  h.textContent = m.title;
  const p = document.createElement('p');
  p.textContent = m.body;
  const time = document.createElement('time');
  time.textContent = new Date(m.receivedAt).toLocaleString();
  card.append(h, p, time);
  (m.buttons || []).forEach(function (b) {
    const btn = document.createElement('button');
    btn.textContent = b.label;
    btn.onclick = function () { WebToApk.markPushRead(m.id); if (b.link) location.href = b.link; };
    card.appendChild(btn);
  });
  return card;
}

if (window.WebToApk && window.WebToApk.getPushInbox) {
  const list = document.getElementById('inbox');
  JSON.parse(WebToApk.getPushInbox() || '[]').forEach(function (m) { list.appendChild(renderCard(m)); });
}

ملاحظات: صندوق الرسائل محفوظ على الهاتف، لذا اقرأه عند فتح شاشتك - الأحداث تصل فقط أثناء ظهور التطبيق على الشاشة. الرسالة التي تسحبها من AppMint تختفي من الصندوق ومن شريط الإشعارات. الرسائل المرسلة بوضع "دفع فقط" (شريط الإشعارات) لا تُضاف إلى الصندوق. يُمرَّر التفصيل نفسه أيضًا إلى window.onAppMintPush(detail) إن عرّفتها.

getPushTags الجسر (bridge)#

window.WebToApk.getPushTags(): String

وسوم هذا التثبيت كسلسلة كائن JSON، مثل {"plan":"pro"}.

مثال

يقرأ وسوم AppMint Push الخاصة بهذا التثبيت.

تُرجع: سلسلة JSON، بشكل متزامن - كائن من قيم نصية، مثل '{"plan":"pro","cart":"open"}'؛ و'{}' عند عدم وجود وسوم. حلّلها. المتطلبات: فعّل AppMint Push في خطوة التكامل (الخطوة 3) عند الإنشاء.

اقرأ وسمًا واحدًا:

function pushTags() {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return {};
  try { return JSON.parse(WebToApk.getPushTags() || '{}'); } catch (e) { return {}; }
}

const plan = pushTags().plan || 'free';

اضبط وسمًا فقط عندما يتغير - فكل استدعاء setPushTag يجعل التثبيت يتصل بـ AppMint Push، لذا تخطَّ ما لا يغيّر شيئًا:

function ensureTag(key, value) {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return;
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  if ((tags[key] || '') !== value) WebToApk.setPushTag(key, value);
}

ensureTag('plan', user.isPro ? 'pro' : 'free');

اسردها على شاشة تصحيح أثناء اختبار الإرسال إلى وسم:

const list = document.getElementById('push-tags');
if (window.WebToApk && window.WebToApk.getPushTags) {
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  list.textContent = Object.keys(tags).length
    ? Object.keys(tags).map(function (k) { return k + ' = ' + tags[k]; }).join('\n')
    : 'No tags yet';
}

ملاحظات: هذا ما يحتفظ به الهاتف؛ يراه مركز الإرسال بعد التسجيل التالي للتثبيت، بعد ثوانٍ من التغيير إذا كان الهاتف متصلًا.

getPushUser الجسر (bridge)#

window.WebToApk.getPushUser(): String

معرّف المستخدم الذي ضُبط آخر مرة بـ setPushUser ("" عند تسجيل الخروج أو إذا لم يُضبط قط).

مثال

يقرأ معرّف المستخدم الذي أعطاه هذا التثبيت آخر مرة لـ AppMint Push بواسطة setPushUser.

تُرجع: string، بشكل متزامن - المعرّف المحفوظ، أو '' عندما لا يكون أحد مسجّل الدخول أو لم يُضبط قط. المتطلبات: فعّل AppMint Push في خطوة التكامل (الخطوة 3) عند الإنشاء.

اضبط المعرّف فقط عندما يتغيّر (عند كل بدء للتطبيق):

function syncPushUser(currentUserId) {
  if (!(window.WebToApk && window.WebToApk.getPushUser)) return;
  const want = currentUserId || '';
  if (WebToApk.getPushUser() !== want) WebToApk.setPushUser(want);
}

syncPushUser(localStorage.getItem('userId'));

امسح معرّفًا قديمًا عندما انتهت جلستك أنت والتطبيق مغلق:

async function checkSession() {
  const session = await myBackend.currentSession();       // null when expired
  if (!session && window.WebToApk && window.WebToApk.getPushUser && WebToApk.getPushUser() !== '') {
    WebToApk.setPushUser('');                              // stop personal messages to this phone
  }
}

checkSession();

اعرضه على شاشة تصحيح أثناء اختبار الإرسال إلى مستخدم واحد:

const info = document.getElementById('push-debug');
if (window.WebToApk && window.WebToApk.getPushUser) {
  const id = WebToApk.getPushUser();
  info.textContent = id ? 'Push user: ' + id : 'Push user: nobody signed in';
} else {
  info.textContent = 'Not inside the app';
}

ملاحظات: هذا ما يحتفظ به الهاتف. يرى مركز الإرسال التغيير بعد التسجيل التالي للتثبيت، بعد ثوانٍ إذا كان الهاتف متصلًا.

markPushRead الجسر (bridge)#

window.WebToApk.markPushRead(id: String): Boolean

يعلّم رسالة دفع واحدة كمقروءة؛ مرّر "" لتعليم كل الرسائل كمقروءة.

مثال

يعلّم رسالة واحدة في صندوق push كمقروءة، أو كلها عندما تمرّر ''.

القيمة المُعادة: true إن تغيرت رسالة؛ false إن لم يتغير شيء (معرّف غير معروف، أو مقروءة بالفعل، أو AppMint Push غير مفعّل). المتطلبات: فعّل AppMint Push في خطوة Integrate (الخطوة 3) عند الإنشاء.

function openMessage(id) {
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  WebToApk.markPushRead(id);                 // one message
  updateBellCount();
}

document.getElementById('mark-all-read').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  WebToApk.markPushRead('');                 // every message
  updateBellCount();
});

function updateBellCount() {
  const inbox = JSON.parse(WebToApk.getPushInbox() || '[]');
  const unread = inbox.filter(function (m) { return !m.read; }).length;
  document.getElementById('bell-count').textContent = unread ? String(unread) : '';
}

علّم الرسالة مقروءة بعد أن يراها المستخدم فعلًا على الشاشة، وليس فقط عند نقره عليها:

if (window.WebToApk && window.WebToApk.markPushRead) {
  const seen = new IntersectionObserver(function (entries) {
    entries.forEach(function (entry) {
      if (!entry.isIntersecting) return;
      const id = entry.target.dataset.msgId;
      seen.unobserve(entry.target);
      if (WebToApk.markPushRead(id)) {       // true only when it was unread
        entry.target.classList.replace('unread', 'read');
        updateBellCount();
      }
    });
  }, { threshold: 0.6 });

  document.querySelectorAll('#inbox li.unread').forEach(function (li) { seen.observe(li); });
}

امسح الشارة عند فتح شاشة الصندوق، وأعد الرسم فقط عندما كان هناك فعلًا ما لم يُقرأ:

function openInboxScreen() {
  showScreen('inbox');
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  const changed = WebToApk.markPushRead('');  // false: nothing was unread
  if (changed) {
    document.getElementById('bell-count').textContent = '';
    renderInbox();
  }
}

ملاحظات: حالة القراءة تُخزَّن على هذا الهاتف فقط. ولا تزيل إشعار شريط الإشعارات.

restorePushMessage الجسر (bridge)#

window.WebToApk.restorePushMessage(id: String): Boolean

تراجع عن الإخفاء: يعيد الرسالة إلى مكانها في الصندوق.

مثال

يتراجع عن dismissPushMessage(id): تعود الرسالة إلى صندوق push في مكانها القديم.

تُرجع: true إذا استُعيدت الرسالة؛ false إذا كان المعرّف غير معروف أو لم تكن مخفية. المتطلبات: فعّل AppMint Push في خطوة التكامل (الخطوة 3) عند الإنشاء.

let lastDismissed = null;

function onSwipe(id) {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return;
  if (WebToApk.dismissPushMessage(id)) {
    lastDismissed = id;
    showUndo();
    renderInbox();
  }
}

document.getElementById('undo-btn').addEventListener('click', function () {
  if (!lastDismissed || !window.WebToApk) return;
  if (WebToApk.restorePushMessage(lastDismissed)) renderInbox();
  lastDismissed = null;
});

تراجع عن عدة عمليات تمرير دفعة واحدة: تذكّر كل رسالة أُخفيت على هذه الشاشة وأعدها جميعًا:

const hiddenThisVisit = [];

function hide(id) {
  if (window.WebToApk && WebToApk.dismissPushMessage(id)) {
    hiddenThisVisit.push(id);
    document.getElementById('undo-all').hidden = false;
    renderInbox();
  }
}

document.getElementById('undo-all').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.restorePushMessage)) return;
  hiddenThisVisit.splice(0).forEach(function (id) { WebToApk.restorePushMessage(id); });
  this.hidden = true;
  renderInbox();
});

اعرض قائمة "المخفية مؤخرًا" يمكن للمستخدم الاستعادة منها لاحقًا. لا تُرجع getPushInbox() الرسائل المخفية، لذا احتفظ بمعرّفاتها وعناوينها بنفسك:

function loadHidden() {
  try { return JSON.parse(localStorage.getItem('hiddenMsgs') || '[]'); } catch (e) { return []; }
}

function renderHidden() {
  const ul = document.getElementById('recently-hidden');
  ul.innerHTML = '';
  loadHidden().forEach(function (h) {
    const li = document.createElement('li');
    li.textContent = h.title + ' ';
    const btn = document.createElement('button');
    btn.textContent = 'Restore';
    btn.onclick = function () {
      WebToApk.restorePushMessage(h.id);     // false: retracted, cleared or already back
      const rest = loadHidden().filter(function (x) { return x.id !== h.id; });
      try { localStorage.setItem('hiddenMsgs', JSON.stringify(rest)); } catch (e) {}
      renderHidden();
      renderInbox();
    };
    li.appendChild(btn);
    ul.appendChild(li);
  });
}

ملاحظات: لا يمكن استعادة رسالة مُسحت بـ clearPushInbox() أو سحبتها أنت من AppMint.

setInAppPresentation الجسر (bridge)#

window.WebToApk.setInAppPresentation(mode: String): Boolean

عرض الرسائل داخل التطبيق: "auto" (الافتراضي) يدع الغلاف يرسم تصميم المنشئ؛ و"none" (يُحفظ) يسلّم كل رسالة إلى الصفحة كـ `appmint:inapp` {type:'shown', presented:false, message} لترسمها بنفسها.

مثال

يختار من يرسم رسالة داخل التطبيق (حملة من مركز إرسال AppMint Push) عند حدوث مشغّلها: التطبيق ('auto'، الافتراضي: تصميم المنشئ من لافتة أو بطاقة وسطية أو ورقة سفلية أو ملء الشاشة) أو صفحتك ('none').

القيمة المُعادة: true عندما حُفظ الوضع (يبقى على الهاتف حتى يتغير)؛ false لأي قيمة أخرى وفي تطبيق بُني بدون AppMint Push. المتطلبات: AppMint Push مفعّل في خطوة Integrate (الخطوة 3) عند الإنشاء.

كل رسالة داخل التطبيق تُطلق الحدث appmint:inapp (وتستدعي window.onAppMintInApp(detail) إن عرّفتها) مع type 'shown'، 'clicked'، 'dismissed' أو 'answered' (إجابة بنجمة أو بدرجة من 0 إلى 10، مع question و value):

window.addEventListener('appmint:inapp', function (e) {
  var d = e.detail;   // { type, id, name, template?, presented?, message?, button?, link?, question?, value? }
  if (d.type === 'shown') analytics('inapp_shown', d.name);
  if (d.type === 'clicked') analytics('inapp_clicked', d.name + ' #' + d.button);
  if (d.type === 'dismissed') analytics('inapp_closed', d.name);
  if (d.type === 'answered') analytics('inapp_answer', d.name + ' ' + d.question + '=' + d.value);
});

ارسم الرسائل بنفسك؛ يسلّم التطبيق كل رسالة إليك بدل عرضها:

if (window.WebToApk && WebToApk.setInAppPresentation) WebToApk.setInAppPresentation('none');

window.onAppMintInApp = function (d) {
  if (d.type !== 'shown' || d.presented) return;   // presented = the app already drew it
  var m = d.message;                                // { title, body, imageUrl, link, inapp, accent?, buttons?, emoji?, question?, lines? }
  showMyDialog(m.title, m.body, (m.buttons || []).map(function (b) {
    return { label: b.label, onClick: function () { if (b.link) location.href = b.link; } };
  }));
};

أعد الخيار إلى التطبيق (مثلًا من مفتاح في الإعدادات):

document.getElementById('native-popups').addEventListener('change', function (e) {
  if (window.WebToApk && WebToApk.setInAppPresentation) {
    WebToApk.setInAppPresentation(e.target.checked ? 'auto' : 'none');
  }
});

ملاحظات: مع 'none' تُحسب الرسالة مع ذلك كمعروضة (تنطبق حدود تكرارها) لحظة تسليمها إلى الصفحة. ضبط 'none' يُغلق أيضًا رسالة يعرضها التطبيق الآن. لرسائل الدفع مفتاحها الخاص، setPushPresentation.

setPushPresentation الجسر (bridge)#

window.WebToApk.setPushPresentation(mode: String): Boolean

العرض الأصلي لرسائل الدفع داخل التطبيق: "auto" (الافتراضي) يتيح للغلاف رسم اللافتة / النافذة / الورقة / بطاقة ملء الشاشة التي اختارها المنشئ أثناء فتح التطبيق؛ "none" يوقفه نهائيًا (يُحفظ) - للتطبيقات التي ترسم بنفسها. الحدث `appmint:push` والصندوق لا يتأثران في الحالتين؛ وعلامة `presented` في الحدث المستلم تقول هل عرضه الغلاف.

مثال

يشغّل عرض التطبيق نفسه لرسائل AppMint Push على الشاشة ('auto'، الافتراضي) أو يوقفه ('none') - للتطبيقات التي ترسم لافتتها الخاصة من حدث appmint:push.

تُرجع: true عند حفظ الوضع؛ false لأي قيمة غير 'auto' أو 'none'. يُحفظ الاختيار على الهاتف ويبقى حتى يتغيّر. المتطلبات: فعّل AppMint Push في خطوة التكامل (الخطوة 3) عند الإنشاء.

ارسم لافتتك الخاصة بدل لافتة الغلاف:

if (window.WebToApk && window.WebToApk.setPushPresentation) {
  WebToApk.setPushPresentation('none');       // 'auto' to give it back to the shell
}

window.addEventListener('appmint:push', function (e) {
  const d = e.detail;
  // { type: 'received', message: {...}, presented: false } — presented = the shell already showed it
  if (d.type === 'received' && !d.presented) showMyBanner(d.message.title, d.message.body, d.message.link);
  if (d.type === 'revoked') hideMyBanner(d.id);
});

دع المستخدم يوقف النوافذ المنبثقة من شاشة إعداداتك. لا توجد دالة قراءة، لذا احتفظ بنسختك من الاختيار لرسم المفتاح:

const toggle = document.getElementById('show-popups');
let saved = null;
try { saved = localStorage.getItem('pushPopups'); } catch (e) {}
toggle.checked = saved !== 'none';

toggle.addEventListener('change', function () {
  if (!(window.WebToApk && window.WebToApk.setPushPresentation)) return;
  const mode = toggle.checked ? 'auto' : 'none';
  if (WebToApk.setPushPresentation(mode)) {
    try { localStorage.setItem('pushPopups', mode); } catch (e) {}
  }
});

مع 'auto'، اعرف ما فعله المستخدم برسالة عرضها التطبيق على الشاشة: opened عندما ضغط عليها (action هو -1 للرسالة نفسها، وإلا فهو فهرس الزر)، closed عندما أغلقها دون الضغط:

window.addEventListener('appmint:push', function (e) {
  const d = e.detail;
  if (d.type === 'opened') {
    // the message is already marked read in the inbox
    track('push_opened', { id: d.id, button: d.action, link: d.link });
    updateBellCount();
  } else if (d.type === 'closed') {
    track('push_closed', { id: d.id });
  }
});

ملاحظات: مع 'auto'، تُعرض الرسالة التي أرسلتها بنمط داخل التطبيق فوق التطبيق وهو مفتوح (ولا توضع بعدها في شريط الإشعارات). مع 'none' لا يرسمها الغلاف أبدًا؛ ويعمل الصندوق (getPushInbox) والحدث appmint:push بالطريقة نفسها في الحالتين. ضبط 'none' يُغلق أيضًا رسالة يعرضها الغلاف الآن.

setPushTag الجسر (bridge)#

window.WebToApk.setPushTag(key: String, value: String): Boolean

يضبط وسمًا واحدًا في AppMint Push (المفتاح: أحرف وأرقام و _ حتى 32؛ القيمة حتى 128) - يمكن لمركز الإرسال استهداف "plan = pro"، ويمكن لأتمتة أن تنطلق بعد مدة محددة من ضبط الوسم (سلة متروكة: اضبط cart=open وامسحه عند الدفع). القيمة الفارغة تزيل الوسم. حتى 50 وسمًا لكل تثبيت.

مثال

يضبط وسم AppMint Push واحدًا على هذا التثبيت، مثل plan = pro أو city = pune. يستطيع مركز الإرسال (Home > Notifications) الإرسال إلى كل من لديه وسم، ويستطيع أتمتة الوسوم إرسال رسالة بعد وقت محدد من ضبط وسم - عربة متروكة، مستوى منتهٍ. القيمة الفارغة تزيل الوسم.

تُرجع: true عند الحفظ؛ false عندما لا يكون المفتاح من 1 إلى 32 حرفًا أو رقمًا أو _، أو تزيد القيمة على 128 حرفًا، أو يصبح للتثبيت أكثر من 50 وسمًا (لا يُحفظ شيء حينها). تُحفظ الوسوم على الهاتف وتُبلَّغ إلى AppMint Push بعد لحظة (أو عند التشغيل التالي إذا كان غير متصل). المتطلبات: فعّل AppMint Push في خطوة التكامل (الخطوة 3) عند الإنشاء. يصل الاستهداف بالوسوم والأتمتة إلى الهواتف التي تشغّل تطبيقًا أُنشئ بـ AppMint اعتبارًا من 30 Sep 2026.

ضع وسمًا على ما اشتراه المستخدم أو اختاره، لتتمكن من الإرسال إلى "plan = pro" فقط:

function pushTag(key, value) {
  if (!(window.WebToApk && window.WebToApk.setPushTag)) return false;
  return WebToApk.setPushTag(key, value);
}

pushTag('plan', user.isPro ? 'pro' : 'free');
pushTag('city', user.city.toLowerCase());

سلة متروكة: اضبط وسمًا عند وجود عناصر في السلة وامسحه عند الدفع. في مركز الإرسال، أضف أتمتة وسم على cart مع تأخير (مثلًا 60 دقيقة)؛ لا يصل التذكير إلى الهاتف إلا إذا كان cart لا يزال مضبوطًا عند انتهاء الوقت:

function onCartChanged(items) {
  if (!(window.WebToApk && window.WebToApk.setPushTag)) return;
  // Setting 'open' again while it is already 'open' does not restart the automation's clock.
  WebToApk.setPushTag('cart', items.length > 0 ? 'open' : '');
}

function onCheckoutComplete() {
  if (window.WebToApk && window.WebToApk.setPushTag) WebToApk.setPushTag('cart', '');   // bought: no reminder
}

امسح الوسوم الخاصة بالمستخدم عند تسجيل الخروج، مع معرّف المستخدم:

function signOutOfPush() {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return;
  WebToApk.setPushUser('');
  WebToApk.setPushTag('plan', '');
  WebToApk.setPushTag('cart', '');
}

ملاحظات: الوسوم تخص هذا التثبيت لا المستخدم المسجّل الدخول. ضبط وسم على قيمته الحالية لا يغيّر شيئًا في الخادم. بعد إزالة وسم، يتيح ضبطه من جديد لأتمتة الوسم أن ترسل مرة أخرى. لا تضع أبدًا عنوان بريد إلكتروني أو رقم هاتف أو أي شيء خاص في وسم.

setPushTags الجسر (bridge)#

window.WebToApk.setPushTags(json: String): Boolean

يضبط عدة وسوم دفعة واحدة من نص كائن JSON؛ القيم "" تزيل.

مثال

يضبط عدة وسوم في AppMint Push دفعة واحدة من سلسلة كائن JSON، مثل '{"plan":"pro","team":"red"}'. القيمة "" تزيل ذلك الوسم؛ والوسوم التي لا تذكرها تبقى.

تُرجع: true عند الحفظ؛ false عندما لا تكون السلسلة كائن JSON، أو أي مفتاح ليس من 1 إلى 32 حرفًا أو رقمًا أو _، أو أي قيمة تزيد على 128 حرفًا، أو يصبح للتثبيت أكثر من 50 وسمًا - وحينها لا يُحفظ شيء إطلاقًا. المتطلبات: فعّل AppMint Push في خطوة التكامل (الخطوة 3) عند الإنشاء. يصل الاستهداف بالوسوم والأتمتة إلى الهواتف التي تشغّل تطبيقًا أُنشئ بـ AppMint اعتبارًا من 30 Sep 2026.

بعد تسجيل الدخول، ضع وسومًا على الملف الشخصي باستدعاء واحد:

function tagProfile(profile) {
  if (!(window.WebToApk && window.WebToApk.setPushTags)) return false;
  return WebToApk.setPushTags(JSON.stringify({
    plan: profile.plan,                   // 'free' | 'pro'
    level: String(profile.level),         // values are text: turn numbers into strings
    team: profile.team || ''              // '' removes the tag
  }));
}

عند تسجيل الخروج، أزلها جميعًا (اقرأ الوسوم الحالية، وأعد إرسال كل منها بقيمة ''):

function clearPushTags() {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return;
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  Object.keys(tags).forEach(function (k) { tags[k] = ''; });
  WebToApk.setPushTags(JSON.stringify(tags));
  WebToApk.setPushUser('');
}

أنهى مستوى: ضع وسمًا، ودع أتمتة وسم على level_done (مثلًا بعد 10 دقائق) ترسل التحدي التالي:

function onLevelComplete(level, score) {
  if (!(window.WebToApk && window.WebToApk.setPushTags)) return;
  WebToApk.setPushTags(JSON.stringify({
    level_done: String(level),            // a new value each level, so the automation can fire again
    last_score: String(score)
  }));
}

ملاحظات: استدعاء واحد هو تغيير واحد: يُحفظ كله أو لا يُحفظ منه شيء. يُبلّغ التثبيت AppMint Push بالوسوم الجديدة بعد لحظة (أو عند التشغيل التالي إذا كان غير متصل).

setPushUser الجسر (bridge)#

window.WebToApk.setPushUser(id: String): Boolean

جمهور AppMint Push: يخبر مركز الإرسال لدى المنشئ بهوية هذا التثبيت، فيمكن إرسال رسالة إلى مستخدم واحد مسجّل الدخول ("u_42") أو إلى كل من لديه وسم معيّن. استدعِها بعد تسجيل الدخول بمعرّف حسابك الثابت؛ ومرّر "" عند تسجيل الخروج. الأحرف والأرقام و _ @ . : + - (حتى 128). تُرجع false لأي شيء آخر، وفي تطبيق لا يستخدم AppMint Push (OneSignal / Firebase خاص بك: لا يُرسل شيء، 2026-10-03). لا تمرّر أبدًا عنوان بريد إلكتروني أو رقم هاتف - استخدم معرّف قاعدة بياناتك.

مثال

يُخبر AppMint Push أيًّا من مستخدميك مسجّل الدخول على هذا الهاتف، فيستطيع مركز الإرسال (الرئيسية > الإشعارات) إرسال رسالة إلى ذلك المستخدم وحده. استدعِها بعد تسجيل الدخول بمعرّف حسابك الثابت؛ واستدعِها بـ '' عند تسجيل الخروج.

القيمة المُعادة: true عندما حُفظ المعرّف؛ false لمعرّف يحتوي على أي شيء غير الحروف والأرقام و_ @ . : + -، أو أطول من 128 حرفًا (ولا يُحفظ شيء حينها). يُحفظ المعرّف على الهاتف حتى تغيّره، ويبلّغ هذا التثبيت AppMint Push به بعد لحظة (أو عند التشغيل التالي إن كان دون اتصال). المتطلبات: فعّل AppMint Push في خطوة Integrate (الخطوة 3) عند الإنشاء. الإرسال إلى مستخدم يصل إلى الهواتف التي تشغّل تطبيقًا بُني بـ AppMint بدءًا من 30 Sep 2026؛ أما الإنشاءات الأقدم فتظل تتلقى الرسائل المرسلة إلى Everyone.

بعد تسجيل الدخول وعند تسجيل الخروج - استخدم معرّف قاعدة بياناتك، وليس عنوان بريد إلكتروني أو رقم هاتف:

function pushUser(id) {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return false;   // browser, or an older build
  return WebToApk.setPushUser(id);
}

async function signIn(email, password) {
  const user = await myBackend.signIn(email, password);   // your own sign-in
  pushUser(user.id);                                      // e.g. 'u_42' or a UUID
}

async function signOut() {
  await myBackend.signOut();
  pushUser('');                                           // this phone is nobody's now
}

Supabase: تابع الجلسة، لتُحسب الجلسة المستعادة كما يُحسب تسجيل الدخول الجديد:

supabase.auth.onAuthStateChange(function (event, session) {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return;
  const id = session ? session.user.id : '';     // the auth UUID: letters, digits and '-'
  if (WebToApk.getPushUser() !== id) WebToApk.setPushUser(id);
});

اجعل المعرّف مقبولًا عندما قد يحتوي معرّفك على أحرف أخرى (مسافات، /، #)، وسجّل الحالة النادرة التي يُرفض فيها رغم ذلك:

function pushUserIdFrom(rawId) {
  return String(rawId).trim().replace(/[^A-Za-z0-9_@.:+\-]/g, '_').slice(0, 128);
}

if (window.WebToApk && window.WebToApk.setPushUser) {
  const ok = WebToApk.setPushUser(pushUserIdFrom(currentUser.id));
  if (!ok) console.warn('push user id refused', currentUser.id);
}

ملاحظات: يصل المستخدم نفسه المسجّل الدخول على هاتفين إلى كليهما. المعرّف يخص هذا التثبيت لا الحساب - يجب أن يمسحه تسجيل الخروج، وإلا يواصل الهاتف استقبال رسائل المستخدم السابق. setPushUser('') لا تمسح الوسوم المضبوطة بـ setPushTag؛ امسح الوسوم الخاصة بالمستخدم عند تسجيل الخروج أيضًا.

trackEvent الجسر (bridge)#

window.WebToApk.trackEvent(name: String, propsJson: String): Boolean

الرسائل داخل التطبيق: يبلّغ عن حدث تستطيع حملات المنشئ أن تعمل عليه (مُشغّل "event"، بالاسم نفسه). propsJson اختياري JSON. الاسم `appmint:route` مع {"path":"/x"} يبلّغ عن تغيير عرض لا يظهره عنوان URL. false لاسم خارج [A-Za-z0-9_.:-]{1,64}، وبدون AppMint Push.

مثال

يُخبر التطبيق بأن شيئًا حدث ("signed_up"، "checkout_done"، "level:3"). تظهر الآن رسالة داخل التطبيق أنشأها المنشئ في مركز إرسال AppMint Push بمشغّل "عند حدوث حدث" وبالاسم نفسه (أو بعد مدة الانتظار التي اختارها المنشئ).

القيمة المُعادة: true عندما قُبل الحدث؛ false لاسم خارج الحروف والأرقام و_ . : - (حتى 64)، وفي تطبيق بُني بدون AppMint Push. AppMint.trackEvent(name, props) هو الاستدعاء نفسه مع props ككائن. المتطلبات: تفعيل AppMint Push في خطوة Integrate (الخطوة 3) عند الإنشاء. propsJson اختياري ('') ولا يُخزَّن.

// After a purchase: the creator's "Thanks, rate us?" message is set to trigger on "purchase_done".
function onPurchaseDone(order) {
  if (window.AppMint && AppMint.trackEvent) {
    AppMint.trackEvent('purchase_done', { total: order.total });
  }
}

تأخذ الجسر الخام الخصائص كسلسلة JSON (مرّر '' إن لم تكن هناك خصائص):

function track(name, props) {
  if (!(window.WebToApk && window.WebToApk.trackEvent)) return false;   // a browser: nothing to show
  return WebToApk.trackEvent(name, props ? JSON.stringify(props) : '');
}
track('level:3');
track('tutorial_skipped', { step: 2 });

يمكن الإبلاغ عن تغيّر الشاشة الذي لا يظهر في العنوان (تبويبات، معالج، "صفحة" نافذة منبثقة) كمسار، فتراه رسائل المنشئ "في صفحة". ويمكن للصفحة أيضًا إرسال الحدث appmint:route مع المسار؛ والتطبيق يستمع إليه:

function showTab(name) {
  document.querySelectorAll('.tab').forEach(function (t) { t.hidden = t.id !== name; });
  if (window.AppMint && AppMint.trackEvent) AppMint.trackEvent('appmint:route', { path: '/' + name });
}

// Same thing, without touching the bridge at all:
window.dispatchEvent(new CustomEvent('appmint:route', { detail: { path: '/cart' } }));

ملاحظات: رسائل المسارات تتبع بالفعل تغيّرات الروابط الحقيقية (تحميل الصفحات، مسارات history.pushState و#/hash) من تلقاء نفسها؛ بلّغ عن مسار فقط للعروض التي لا يتغيّر فيها العنوان. الأحداث لا تُحسب ولا تُخزَّن في أي مكان؛ هي فقط تحدد هل تظهر رسالة.

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

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