pub-sub-api-node-client icon indicating copy to clipboard operation
pub-sub-api-node-client copied to clipboard

Unable to get pass through demo code due to auth issue

Open hirenchauhan2 opened this issue 9 months ago • 2 comments

Hi, I'm not able to run the sample code to subscribe to platform event. I'm able to authenticate via JWT flow after you fixed the issue with Private Key checks(thanks!). I can see in the logs that it's getting connected to Salesforce and authenticate successfully, but fails to subscribe with auth error. Could you please guide me why I'm getting this error?

I've checked these links and have followed the cases which are applicable for this error.

https://developer.salesforce.com/docs/platform/pub-sub-api/guide/handling-errors.html https://developer.salesforce.com/docs/platform/pub-sub-api/guide/considerations.html#ip-address-session-setting

Here's the log of program.

[2025-04-25 20:45:07.936 +0530] INFO: Connected to Salesforce org https://[org-alias].sandbox.my.salesforce.com (ORG ID) as [[email protected]]
[2025-04-25 20:45:07.977 +0530] INFO: Connected to Pub/Sub API endpoint api.pubsub.salesforce.com:7443
[2025-04-25 20:45:07.978 +0530] INFO: Authenticated successfully
    connectivityState: 0
[2025-04-25 20:45:07.981 +0530] INFO: /event/SFPlatformEvent__e - Subscribe request sent for 100 events
[2025-04-25 20:45:09.048 +0530] ERROR: /event/SFPlatformEvent__e - gRPC stream error: {"code":16,"details":"An authentication exception occurred. Provide valid authentication via metadata headers. rpcId: 959a9bb0-8321-4bf2-b5bf-b3ad61d2ecfc","metadata":{"content-type":["application/grpc"],"error-code":["sfdc.platform.eventbus.grpc.service.auth.error"],"rpc-id":["959a9bb0-8321-4bf2-b5bf-b3ad61d2ecfc"],"type":["Subscribe"],"date":["Fri, 25 Apr 2025 15:15:00 GMT"]}}
[2025-04-25 20:45:09.049 +0530] ERROR: {
  "code": 16,
  "details": "An authentication exception occurred. Provide valid authentication via metadata headers. rpcId: 959a9bb0-8321-4bf2-b5bf-b3ad61d2ecfc",
  "metadata": {
    "content-type": [
      "application/grpc"
    ],
    "error-code": [
      "sfdc.platform.eventbus.grpc.service.auth.error"
    ],
    "rpc-id": [
      "959a9bb0-8321-4bf2-b5bf-b3ad61d2ecfc"
    ],
    "type": [
      "Subscribe"
    ],
    "date": [
      "Fri, 25 Apr 2025 15:15:00 GMT"
    ]
  }
}
[2025-04-25 20:45:09.049 +0530] INFO: /event/SFPlatformEvent__e - gRPC stream status: {"code":16,"details":"An authentication exception occurred. Provide valid authentication via metadata headers. rpcId: 959a9bb0-8321-4bf2-b5bf-b3ad61d2ecfc","metadata":{"content-type":["application/grpc"],"error-code":["sfdc.platform.eventbus.grpc.service.auth.error"],"rpc-id":["959a9bb0-8321-4bf2-b5bf-b3ad61d2ecfc"],"type":["Subscribe"],"date":["Fri, 25 Apr 2025 15:15:00 GMT"]}}
[2025-04-25 20:45:09.049 +0530] INFO: /event/SFPlatformEvent__e - gRPC stream ended
[2025-04-25 20:45:09.049 +0530] INFO: Client shut down gracefully.

And here's my code:

import * as fs from "fs";
import PubSubApiClient from "salesforce-pubsub-api-client";
import dotenv from "dotenv";
import pino from "pino";

dotenv.config();

const logger = pino({
  transport: {
    target: "pino-pretty",
    options: {
      colorize: true,
      translateTime: "SYS:standard",
      ignore: "pid,hostname",
    },
  },
  level: process.env.LOG_LEVEL || "info",
});
export async function getPubSubClient() {
  const privateKeyPath =
    process.env.SALESFORCE_KEY_PATH || "../keys/private.key";
  const privateKey = fs.readFileSync(privateKeyPath, "utf8");

  const jwtFlowConfig = {
    authType: "oauth-jwt-bearer",
    loginUrl: process.env.SALESFORCE_JWT_LOGIN_URL,
    clientId: process.env.SALESFORCE_CLIENT_ID,
    username: process.env.SALESFORCE_LOGIN_USERNAME,
    privateKey,
  };

  const pubsubClient = new PubSubApiClient(jwtFlowConfig, logger);
  await pubsubClient.connect();
  return pubsubClient;
}
getPubSubClient()
  .then(async (pubsubClient) => {
    logger.info(
      {
        connectivityState: await pubsubClient.getConnectivityState(),
      },
      "Authenticated successfully"
    );
    const eventChanel = "/event/SFPlatformEvent__e";
    // Prepare event callback
    const subscribeCallback = (
      subscription: any,
      callbackType: any,
      data: any
    ) => {
      switch (callbackType) {
        case "event":
          // Event received
          logger.info(
            `${subscription.topicName} - Handling ${data.payload.ChangeEventHeader.entityName} change event ` +
              `with ID ${data.replayId} ` +
              `(${subscription.receivedEventCount}/${subscription.requestedEventCount} ` +
              `events received so far)`
          );
          // Safely log event payload as a JSON string
          logger.info(
            JSON.stringify(
              data,
              (key, value) =>
                /* Convert BigInt values into strings and keep other types unchanged */
                typeof value === "bigint" ? value.toString() : value,
              2
            )
          );
          break;
        case "lastEvent":
          // Last event received
          logger.info(
            `${subscription.topicName} - Reached last of ${subscription.requestedEventCount} requested event on channel. Closing connection.`
          );
          break;

        case "error":
          // Stream error
          logger.error(
            JSON.stringify(
              data,
              (key, value) =>
                /* Convert BigInt values into strings and keep other types unchanged */
                typeof value === "bigint" ? value.toString() : value,
              2
            ),
            "gRPC stream error: "
          );
          break;
        case "end":
          // Client closed the connection
          logger.info("Client shut down gracefully.");
          break;
      }
    };

    // Subscribe to 3 account change event
    pubsubClient.subscribe(eventChanel, subscribeCallback);
  })
  .catch((err) => {
    logger.error(err, "Error in authentication");
  });

hirenchauhan2 avatar Apr 25 '25 15:04 hirenchauhan2

Hi,

I was missing some permissions on the connected app and I don't know which ones were causing the auth issue. So, for now I've added all of them to my connected app and it's started to working. I'll just have to check removing one by one to keep the required permissions only. I'm very new to Salesforce side and don't know much on the integrating with external systems much, I've synced data to Salesforce, but not from Salesforce to external systems.

I've few question now, I would appreciate if anyone can answer these for me!

  1. Should I use this library or use the jsforce streaming client to subscribe to the platform events? as both of them seems to work
  2. jsforce streaming client uses Faye internally to subscribe to Salesforce cometd server and this library uses the gRPC with avro-js to connect to Salesforce(I hope I got it right),. Which one is more future proof and more reliable in terms of data transfers, governor limits, connection reliability, auth retry on token expiry, error handling, etc.?

hirenchauhan2 avatar Apr 26 '25 15:04 hirenchauhan2

Hi, I'm glad that you were able to make it work. You generally only need the api OAuth scope in the connected app and the right permissions for the events to work with the API.

Yes, as you pointed out, this library uses the Pub/Sub API (gRPC based) which is newer and more performant that the legacy Streaming API (CometD based) that jsforce uses. The Pub/Sub API is where Salesforce is investing: it already supports features that the Streaming API will never support: HTTP 2, binary compression with Avro, managed event subscriptions and Platform Event publishing (CometD is read-only). So, yes, the Pub/Sub API is definitely the one that is future-proof.

pozil avatar Apr 26 '25 17:04 pozil