pusher / push-notifications-web

Beams Browser notifications
MIT License
39 stars 20 forks source link

Error: Could not add Device Interest. SDK not registered with Beams. Did you call .start? #98

Closed IceBotYT closed 3 years ago

IceBotYT commented 3 years ago

Hi there. I'm trying to create a push notification service for weather alerts, and having the user sign in with GitHub, but when I set up .setUserId, and I try to add an interest, I get this error: Error: Could not add Device Interest. SDK not registered with Beams. Did you call .start?

Here is my server code running the token provider:

app.get("/pusher/beams-auth", iemCheckAuthenticated, function (req, res) {
  const userId = req.user.id
  const userIdInQueryParam = req.query['user_id']
  if (userId != userIdInQueryParam) {
    res.send(401, "Inconsistent request")
  } else {
    const beamsToken = beamsClient.generateToken(userId);
    res.send(beamsToken)
  }
})

It generates fine and returns fine.

Here is my front-end code. Warning: It gets messy.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>IEMBot Push</title>
        <!-- skipping past all the stylesheets and libraries -->
        <script src="https://js.pusher.com/beams/1.0/push-notifications-cdn.js"></script>
        <script>
          const githubUserId = <%=user.id%>
          const beamsTokenProvider = new PusherPushNotifications.TokenProvider({
            url: "https://msbc.one/pusher/beams-auth",
            queryParams: {
              user_id: githubUserId.toString(),
            },
          });
          const beamsClient = new PusherPushNotifications.Client({
            instanceId: '<redacted>',
          });
        </script>
        <!-- skipping past some css-->
    </head>
    <body>
        <div class="container">
            <h1>Hey there <%= user.username %>!</h1>
            <div id="notifs-on" style="display: none;">
                <p>Alright! You can now manage your offices below.</p>
                <div class="form-inline">
                  <label class="sr-only" for="new-office">Input Office</label>
                  <div class="autocomplete">
                    <input id="new-office" class="form-control" placeholder="Select a room..." autocomplete="off">
                  </div>
                  <button class="btn btn-success" onclick="addInterest()" style="margin-left: 5px;">Add <i class="fas fa-plus"></i></button>
                </div>
                <ul class="offices" id="offices">

                </ul>
            </div>
            <div id="notifs-off" style="display: none;">
                <p>It looks like you have notifications off! To turn them on, please click the button below, and click Allow.</p>
                <button class="btn btn-primary" onclick="askForNotifs()">Enable Notifications <i class="fas fa-bell"></i></button>
            </div>
            <form action="/logout?_method=DELETE" method="POST" id="logout_form">
              <button class="btn btn-danger" type="submit" style="margin-top: 5px;" id="logout_button">Log Out</button>
            </form>
        </div>
        <script src="client.js"></script>
    </body>
</html>

And here's my client code:

const offices = JSON.parse(
  'super long array of offices, I put them here: https://gist.github.com/IceBotYT/40db10abae12c4c89f625b13cb8e6165'
);
window.onload = function () {
  if (!("serviceWorker" in navigator)) {
    return swal({
      title: "Browser not supported",
      text: "Your browser can't use service workers, so you can't use this service. Sorry!",
      icon: "error",
      buttons: false,
      closeOnEsc: false,
      closeOnClickOutside: false,
    });
  }
  if (!("PushManager" in window)) {
    return swal({
      title: "Browser not supported",
      text: "Your browser doesn't have the Push API, so you can't use this service. Sorry!",
      icon: "error",
      buttons: false,
      closeOnEsc: false,
      closeOnClickOutside: false,
    });
  }

  beamsClient
    .getRegistrationState()
    .then((state) => {
      let states = PusherPushNotifications.RegistrationState;
      switch (state) {
        case states.PERMISSION_DENIED: {
          swal({
            title: "Notifications Denied",
            icon: "error",
            text: "Please enable notifications to continue!",
            buttons: false,
            closeOnEsc: false,
            closeOnClickOutside: false,
          });
          break;
        }
        case states.PERMISSION_GRANTED_REGISTERED_WITH_BEAMS: {
          document.getElementById("notifs-on").style.display = "block";
          initialize();
          break;
        }
        case states.PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS: {
          document.getElementById("notifs-on").style.display = "block";
          initialize();
          break;
        }
        case states.PERMISSION_PROMPT_REQUIRED: {
          document.getElementById("notifs-off").style.display = "block";
          break;
        }
      }
    })
    .catch((e) => console.error("Could not get registration state", e));
  autocomplete(document.getElementById("new-office"), offices);
  document.addEventListener("click", function (e) {
    if (e.target == document.getElementById("new-office")) return;
    const value = document.getElementById("new-office").value;
    let good = false;
    for (let office of offices) {
      if (value == office.text) good = true;
    }
    if (good) return;
    document.getElementById("new-office").value = "";
  });
};

function askForNotifs() {
  beamsClient.start().then(() => {
    beamsClient.getRegistrationState().then((state) => {
      let states = PusherPushNotifications.RegistrationState;
      switch (state) {
        case states.PERMISSION_DENIED: {
          swal({
            title: "Notifications Denied",
            icon: "error",
            text: "Please enable notifications to continue!",
            buttons: false,
            closeOnEsc: false,
            closeOnClickOutside: false,
          });
          break;
        }
        case states.PERMISSION_GRANTED_REGISTERED_WITH_BEAMS: {
          let notification = new Notification(
            "Thank you for enabling notifications!"
          );
          document.getElementById("notifs-off").style.display = "none";
          document.getElementById("notifs-on").style.display = "block";
          initialize();
          break;
        }
      }
    });
  });
}

console.log(beamsTokenProvider)
let deviceInterests;
function initialize() {
  beamsClient.start().then(() => {
    console.log("setuserid called");
    beamsClient
      .setUserId(githubUserId.toString(), beamsTokenProvider)
      .then(async () => {
        console.log("setuserid response");
        const beamsUserId = await beamsClient.getUserId();
        if (beamsUserId !== githubUserId) {
          return await beamsClient.stop();
        }
        document
          .getElementById("logout_form")
          .addEventListener("submit", function (e) {
            e.preventDefault();
            beamsClient
              .stop()
              .then(() => document.getElementById("logout_button").click())
              .catch((error) => console.error(error));
          });
      })
      .catch(console.error);
    beamsClient
      .getDeviceInterests()
      .then((interests) => {
        deviceInterests = interests;
        for (let office of interests) {
          let b = document.createElement("li");
          b.classList.add("office-item");
          b.setAttribute("id", office);
          let officeInfo;
          for (let officeIL of offices) {
            if (officeIL.code == office) officeInfo = officeIL;
          }
          b.innerHTML = officeInfo.name + " ";

          let d = document.createElement("button");
          d.classList.add("btn");
          d.classList.add("btn-danger");
          d.classList.add("btn-sm");
          d.setAttribute("onclick", "deleteInterest(this.parentNode.id)");
          d.innerHTML = '<i class="fas fa-trash"></i>';

          b.appendChild(d);

          document.getElementById("offices").appendChild(b);
        }
      })
      .catch((e) => console.error("Could not get device interests", e));
  });
}

let selectedOffice;
function addInterest() {
  if (deviceInterests.includes(selectedOffice))
    return swal({
      title: "Already added",
      text: "You already have this office added to your account!",
      icon: "error",
    });
  beamsClient
    .addDeviceInterest(selectedOffice) // This is where the error happens! ⚠
    .then(async () => {
      console.log("response");
      const userId = await beamsClient.getUserId();
      fetch("/iembot/addoffice?office=" + selectedOffice + "&userId=" + userId)
        .then((resp) => {
          return resp.text();
        })
        .then((status) => {
          if (status == "OK") {
            location.reload();
          } else {
            beamsClient.removeDeviceInterest(selectedOffice).then(() => {
              console.error(
                "Something went wrong while adding an office. " + status
              );
              swal({
                title: "Error",
                text: "An error occured while adding your office.",
                icon: "error",
              });
            });
          }
        });
    })
    .catch(console.error);
}

function autocomplete(inp, arr) {
  // irrelevant
}

function deleteInterest(office) {
  beamsClient.removeDeviceInterest(office).then(() => {
    fetch("/iembot/deleteoffice?office=" + office)
      .then((resp) => {
        return resp.text();
      })
      .then((status) => {
        if (status == "OK") {
          location.reload();
        } else {
          beamsClient.addDeviceInterest(office).then(() => {
            console.error(
              "Something went wrong while adding an office. " + status
            );
            swal({
              title: "Error",
              text: "An error occured while deleting your office.",
              icon: "error",
            });
          });
        }
      });
    location.reload();
  });
}

Sorry if it's really long...

You may also visit the website here. Thank you for your help!

P.S. If you have any tips for making these issues better, please tell me!

benw-pusher commented 3 years ago

Using the Chrome debug console and breakpoints I think the issue here is the scoping of the beamsClient instance. The beamsClient that works (for setting the user ID and initial registration) has a different scope that the beamsClient that is causing an error, which believe is contributing to the issue. Perhaps it would be good to add the beamsClient to a global declaration within the JS file rather than embedded in the HTML <script> tag.

IceBotYT commented 3 years ago

That seems like a good idea

IceBotYT commented 3 years ago

Nope, it's available now, but still not fixed.

IceBotYT commented 3 years ago

Ok, so I just made some changes, and now, after setUserId() gets called, the registration state becomes PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS. Do you have any solutions to this? You can check the registration state with checkRegistrationState().

IceBotYT commented 3 years ago

Aha! I fixed it. Apparently, once you call setUserId(), the user becomes unregistered, so you have to call .start() again for it to work! 🎉