Bring Push Notifications Back!

Dear All, @Robert_Petitto @NoCodeAndy @Darren_Murphy @Jeff_Hager @ThinhDinh,

I am using this Feature Requests to call on all Glide Experts to see if we can work on a good push notification solution I managed to get working on Laptops and on some phones like Samsnung.

OneSignal has an unlimited push notification on its free plan that I was able to explore. I got it working perfectly on Laptops and some phones, but I am sure that with the broad knowledge of our experts, we can work together to find a lasting solution while we wait for Glide.

I set up a Push notification app on OneSignal free account where I generated OneSignal App ID, OneSignal Safari Web ID, API Key and also a Webhook from Make which I used in the code I pasted below (with the help of ChatGPT of course). I saved the code as HTML file and upload on Netlfy (http://netlify.com) to generate a Glide link: https://xyz.netlify.app//notifications.html?glideRowId=2 and use the link in my Glideapp. The RowId=2 is because I used Google Sheet to target each user, but if you use Glide tables, you can use Glide Row ID.

What this will do is that when user clicked on the link, it will take them to the browser where they will click on the enable notification button. This will retrieve the Player ID for that device, send it through the webhook from Make and from there send it to Google sheet or Glide table.

I am available to answer any question on the process I followed and like I said this worked well on my Samsung phone and I was able to send push notifications smoothly, but for some reason it didn’t work on my Infinix phone. Maybe other phones might have the issue. It also worked well on my laptop as well.

So I am thinking that if we can all pull our knowledge and resources together, we will be able to get this to work on all devices. I am positive that we can do it.

See the code below:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{PasteHere}} - Enable Notifications</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      max-width: 500px;
      margin: 0 auto;
      padding: 20px;
      text-align: center;
      background-color: #f9f9f9;
    }
    .logo {
      max-width: 200px;
      margin-bottom: 20px;
    }
    button {
      background: #4CAF50;
      color: white;
      border: none;
      padding: 12px 24px;
      font-size: 16px;
      border-radius: 8px;
      cursor: pointer;
      margin: 10px 0;
      transition: background 0.3s;
    }
    button:hover {
      background: #3e8e41;
    }
    button.secondary {
      background: #f0f0f0;
      color: #333;
    }
    .loader {
      border: 4px solid #f3f3f3;
      border-top: 4px solid #3498db;
      border-radius: 50%;
      width: 30px;
      height: 30px;
      animation: spin 1s linear infinite;
      margin: 20px auto;
      display: none;
    }
    .status {
      margin: 15px 0;
      padding: 10px;
      border-radius: 5px;
      min-height: 50px;
    }
    .success {
      background-color: #d4edda;
      color: #155724;
    }
    .error {
      background-color: #f8d7da;
      color: #721c24;
    }
    .hidden {
      display: none;
    }
    #recovery-options {
      margin-top: 20px;
      text-align: left;
    }
    #recovery-options ol {
      text-align: left;
      margin: 10px 20px;
    }
    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
  </style>
</head>
<body>
  <img src="{{PasteHere}}" alt="Logo" class="logo" />

  <h2>Enable Push Notifications for {{PasteHere}}</h2>
  <p>Stay updated with announcements and events.</p>

  <button id="enableBtn">Allow Notifications</button>
  <div id="loader" class="loader"></div>
  <div id="status" class="status"></div>

  <div id="recovery-options" class="hidden">
    <h3>Device Configuration Needed:</h3>
    <div id="recovery-steps"></div>
    <button onclick="retryWithAlternativeMethod()" class="secondary">Try Again</button>
    <button onclick="window.location.reload()" class="secondary">Refresh Page</button>
  </div>

  <script>
    const ONE_SIGNAL_APP_ID = "{{PasteHere}}";
    const MAKE_WEBHOOK_URL = "{{PasteHere}}";

    const DEVICE = {
      isAndroid: /Android/.test(navigator.userAgent),
      isIOS: /iPhone|iPad|iPod/.test(navigator.userAgent),
      isOldChrome: /Chrome\/([0-9]+)/.test(navigator.userAgent) && parseInt(RegExp.$1) < 80,
      isManufacturerBrowser: /SamsungBrowser|MiuiBrowser|HUAWEI/.test(navigator.userAgent),
      hasDataSaver: navigator.connection?.saveData,
      hasStorageAccess: testStorageAccess()
    };

    function testStorageAccess() {
      try {
        localStorage.setItem('test', '1');
        localStorage.removeItem('test');
        return true;
      } catch {
        return false;
      }
    }

    const enableBtn = document.getElementById('enableBtn');
    const loader = document.getElementById('loader');
    const statusEl = document.getElementById('status');
    const recoveryOptions = document.getElementById('recovery-options');
    const recoverySteps = document.getElementById('recovery-steps');

    function showLoader() {
      loader.style.display = 'block';
      enableBtn.style.display = 'none';
      statusEl.textContent = "Setting up notifications...";
    }

    function hideLoader() {
      loader.style.display = 'none';
    }

    function updateStatus(message, isSuccess = false) {
      statusEl.textContent = message;
      statusEl.className = isSuccess ? 'status success' : 'status error';
      console.log(message);
    }

    async function sendToMake(playerId) {
      const glideRowId = new URLSearchParams(window.location.search).get('glideRowId');
      if (!glideRowId) {
        updateStatus('Error: Missing user reference', false);
        return false;
      }

      try {
        const response = await fetch(MAKE_WEBHOOK_URL, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            glideRowId: glideRowId,
            playerId: playerId,
            timestamp: new Date().toISOString()
          })
        });

        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        return true;
      } catch (error) {
        console.error('Webhook error:', error);
        return false;
      }
    }

    async function getPlayerIdStandard() {
      return new Promise((resolve) => {
        OneSignalDeferred.push(async (OneSignal) => {
          try {
            await OneSignal.init({
              appId: ONE_SIGNAL_APP_ID,
              safari_web_id: "{{PasteHere}}",
              serviceWorkerParam: {
                scope: "/",
                enable: !DEVICE.isManufacturerBrowser
              }
            });

            const subscription = OneSignal.User.PushSubscription;
            if (subscription.id) {
              resolve(subscription.id);
            } else {
              const listener = (event) => {
                if (event.current.id) {
                  OneSignal.User.PushSubscription.removeEventListener('change', listener);
                  resolve(event.current.id);
                }
              };
              OneSignal.User.PushSubscription.addEventListener('change', listener);
              setTimeout(() => {
                OneSignal.User.PushSubscription.removeEventListener('change', listener);
                resolve(null);
              }, 8000);
            }
          } catch (error) {
            console.error('Standard method failed:', error);
            resolve(null);
          }
        });
      });
    }

    async function getPlayerIdIframeFallback() {
      return new Promise((resolve) => {
        const iframe = document.createElement('iframe');
        iframe.style.display = 'none';
        iframe.sandbox = 'allow-scripts allow-same-origin';
        iframe.srcdoc = `
          <script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js"><\/script>
          <script>
            window.OneSignalDeferred = window.OneSignalDeferred || [];
            OneSignalDeferred.push(async (OneSignal) => {
              await OneSignal.init({
                appId: "${ONE_SIGNAL_APP_ID}",
                serviceWorker: false
              });
              const subscription = OneSignal.User.PushSubscription;
              window.parent.postMessage({ playerId: subscription.id }, '*');
            });
          <\/script>
        `;

        const timeout = setTimeout(() => {
          window.removeEventListener('message', listener);
          iframe.remove();
          resolve(null);
        }, 8000);

        const listener = (event) => {
          if (event.data?.playerId) {
            clearTimeout(timeout);
            window.removeEventListener('message', listener);
            iframe.remove();
            persistPlayerId(event.data.playerId);
            resolve(event.data.playerId);
          }
        };

        window.addEventListener('message', listener);
        document.body.appendChild(iframe);
      });
    }

    function persistPlayerId(id) {
      try {
        localStorage.setItem('onesignal_player_id', id);
        sessionStorage.setItem('onesignal_player_id', id);
        document.cookie = `onesignal_player_id=${id}; max-age=2592000; path=/; SameSite=Lax`;
      } catch (error) {
        console.error('Persistence failed:', error);
      }
    }

    function getPersistedPlayerId() {
      try {
        return (
          localStorage.getItem('onesignal_player_id') ||
          sessionStorage.getItem('onesignal_player_id') ||
          document.cookie.match(/(^|;)onesignal_player_id=([^;]+)/)?.[2] ||
          null
        );
      } catch {
        return null;
      }
    }

    async function initializeOneSignal() {
      showLoader();
      recoveryOptions.classList.add('hidden');

      try {
        if (!window.OneSignalDeferred) {
          window.OneSignalDeferred = [];
          const script = document.createElement('script');
          script.src = 'https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js';
          script.defer = true;
          document.head.appendChild(script);
        }

        let playerId = await getPlayerIdStandard();

        if (!playerId && (DEVICE.isAndroid || DEVICE.isManufacturerBrowser)) {
          console.log('Trying iframe fallback...');
          playerId = await getPlayerIdIframeFallback();
        }

        if (!playerId) {
          playerId = getPersistedPlayerId();
          if (playerId) console.log('Using persisted Player ID');
        }

        if (!playerId) throw new Error('Could not get Player ID');

        const success = await sendToMake(playerId);
        if (success) {
          updateStatus("Success! Notifications enabled.", true);
          if (DEVICE.isIOS) {
            setTimeout(() => {
              window.location.href = "{{PasteHere}}";
            }, 2000);
          }
        } else {
          throw new Error('Failed to save settings');
        }
      } catch (error) {
        console.error("Error:", error);
        showRecoverySteps();
        updateStatus(`Error: ${error.message}`, false);
      } finally {
        hideLoader();
      }
    }

    function showRecoverySteps() {
      const solutions = [
        DEVICE.isOldChrome && 'Update Chrome browser',
        DEVICE.isManufacturerBrowser && 'Use standard Chrome browser instead',
        DEVICE.hasDataSaver && 'Disable Data Saver mode in Chrome settings',
        !DEVICE.hasStorageAccess && 'Enable cookies and site data'
      ].filter(Boolean);

      recoverySteps.innerHTML = solutions.length 
        ? `<ol>${solutions.map(s => `<li>${s}</li>`).join('')}</ol>`
        : '<p>Please try again or contact support.</p>';

      recoveryOptions.classList.remove('hidden');
    }

    async function retryWithAlternativeMethod() {
      showLoader();
      recoveryOptions.classList.add('hidden');
      updateStatus("Trying alternative method...");

      try {
        const playerId = await getPlayerIdIframeFallback();
        if (!playerId) throw new Error('Alternative method failed');

        const success = await sendToMake(playerId);
        if (success) {
          updateStatus("Success! Notifications enabled.", true);
        } else {
          throw new Error('Failed to save settings');
        }
      } catch (error) {
        updateStatus(`Error: ${error.message}`, false);
        showRecoverySteps();
      } finally {
        hideLoader();
      }
    }

    enableBtn.addEventListener('click', initializeOneSignal);

    if (DEVICE.isIOS) {
      statusEl.innerHTML = `
        <strong>iPhone Users:</strong><br>
        1. Tap "Allow Notifications"<br>
        2. Accept the Safari permission prompt<br>
        3. Open from home screen for best experience
      `;
    }

    if (DEVICE.isAndroid) {
      const issues = [
        DEVICE.isManufacturerBrowser && 'manufacturer browser',
        DEVICE.hasDataSaver && 'data saver mode',
        !DEVICE.hasStorageAccess && 'storage access blocked'
      ].filter(Boolean);

      if (issues.length) {
        statusEl.innerHTML = `
          <strong>Android Detected:</strong><br>
          ${issues.join(', ')} may affect notifications
        `;
      }
    }
  </script>
</body>
</html>
{{PasteHere}} - Enable Notifications body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 500px; margin: 0 auto; padding: 20px; text-align: center; background-color: #f9f9f9; } .logo { max-width: 200px; margin-bottom: 20px; } button { background: #4CAF50; color: white; border: none; padding: 12px 24px; font-size: 16px; border-radius: 8px; cursor: pointer; margin: 10px 0; transition: background 0.3s; } button:hover { background: #3e8e41; } button.secondary { background: #f0f0f0; color: #333; } .loader { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; margin: 20px auto; display: none; } .status { margin: 15px 0; padding: 10px; border-radius: 5px; min-height: 50px; } .success { background-color: #d4edda; color: #155724; } .error { background-color: #f8d7da; color: #721c24; } .hidden { display: none; } #recovery-options { margin-top: 20px; text-align: left; } #recovery-options ol { text-align: left; margin: 10px 20px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } Logo

Enable Push Notifications for {{PasteHere}}

Stay updated with announcements and events.

Allow Notifications

Device Configuration Needed:

Try Again Refresh Page

The code may look scattered cos I remove some sensitive information, but I am available to answer any questions you might have. Thank you