adjust / web_sdk

41 stars 25 forks source link

web sdk does not work with SSR apps #23

Closed adrienlo closed 2 years ago

adrienlo commented 3 years ago

Because of the references to window, the sdk doesn't work. Was this meant to work with SSR apps?

error - ReferenceError: window is not defined
    at eval (webpack-internal:///./pages/_app.jsx:54:1)
    at Module../pages/_app.jsx (/Users/Work/web/.next/server/pages/_app.js:139:1)
YaraMatkova commented 3 years ago

Hi @adrienlo, sorry for a pretty long delay.

Yes, Web SDK uses some client-side Web APIs. You could workaround this and import the SDK only when window is defined.

If it's applicable to your project you can combine this approach with React Context:

import { createContext, useContext } from 'react';

const AdjustContext = createContext();

export function AdjustWrapper({ children }) {
  let AdjustSdk

  if (typeof window !== 'undefined') {
    AdjustSdk = require('@adjustcom/adjust-web-sdk')

    AdjustSdk.initSdk({
      appToken: "YOUR_APP_TOKEN",
      environment: "production", // or "sandbox"
      // ... other optional parameters if needed
    })
  }

  return (
    <AdjustContext.Provider value={AdjustSdk}>
      {children}
    </AdjustContext.Provider>
  );
}

export function useAdjustContext() {
  return useContext(AdjustContext);
}

Use this AdjustWrapper in top-level component of your app:

import { AdjustWrapper } from '../libs/adjust'

export default function App() {
  return (
    <AdjustWrapper>
      {/* other components here */}
    </AdjustWrapper>
  )
}

and add useAdjustContext hook to get AdjustSdk when it's needed in components:

import { useAdjustContext } from '../libs/adjust'

export function TrackEventButton() {
  const adjustSdk = useAdjustContext()

  return (
    <button onClick={() => {
      adjustSdk.trackEvent({ eventToken: 'EVENT_TOKEN' })
    }}>Track event</button>
  )
}

Let me know if you still have questions

AntoineBouquet commented 2 years ago

Hi @YaroslavnaMatkova

I have the same need to use Adjust SDK in a Angular project build in SSR mode.

I tried

import * as Adjust from '@adjustcom/adjust-web-sdk';

[...]
export class AppComponent implements OnInit {
   ngOnInit() {
       Adjust.default.initSdk({
         appToken: 'token',
         environment:  'sandbox'
       });
   }
}

and I get this error :

})(window, function () {
   ^
ReferenceError: window is not defined

I just need to track how many visitors my website have, so I should need only initSdk for my purpose.

What solution could I implement for this ? Do I really need to use this web SDK ?

AntoineBouquet commented 2 years ago

I may found a solution : if I use the inline script directly on my index.html of my Angular project, I don't have build SSR compilation error.

<head>
  <script type="text/javascript">
      !function(t,a,e,r,s,l,d,n,o){t.Adjust=t.Adjust||{},t.Adjust_q=t.Adjust_q||[];for(var c=0;c<l.length;c++)d(t.Adjust,t.Adjust_q,l[c]);n=a.createElement("script"),o=a.getElementsByTagName("script")[0],n.async=!0,n.src="https://cdn.adjust.com/adjust-5.2.1.min.js",n.onload=function(){for(var a=0;a<t.Adjust_q.length;a++)t.Adjust[t.Adjust_q[a][0]].apply(t.Adjust,t.Adjust_q[a][1]);t.Adjust_q=[]},o.parentNode.insertBefore(n,o)}(window,document,0,0,0,["initSdk","trackEvent","addGlobalCallbackParameters","addGlobalPartnerParameters","removeGlobalCallbackParameter","removeGlobalPartnerParameter","clearGlobalCallbackParameters","clearGlobalPartnerParameters","switchToOfflineMode","switchBackToOnlineMode","stop","restart","gdprForgetMe","disableThirdPartySharing"],function(t,a,e){t[e]=function(){a.push([e,arguments])}});
    </script>
</head>

<body>
  [...]

  <script type="text/javascript">
    Adjust.initSdk({
      appToken: 'token',
      environment: 'sandbox',
    });
  </script>
</body>
YaraMatkova commented 2 years ago

Hey @AntoineBouquet,

I suggest to workaround this issue using dynamic import depending on platform:

import { PLATFORM_ID, Inject, Injectable } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

import type AdjustSDK from "@adjustcom/adjust-web-sdk";

@Injectable({ providedIn: 'root' })
export class AdjustService {
    private Adjust: typeof AdjustSDK | null = null;

    constructor(@Inject(PLATFORM_ID) private platformId: Object) { }

    async init() {
        if (isPlatformBrowser(this.platformId)) {
            this.Adjust = (await import("@adjustcom/adjust-web-sdk")).default;

            this.Adjust.initSdk({
                appToken: "YOUR_APP_TOKEN",
                environment: "sandbox",
                logLevel: "verbose"
            });
        }
    }

    trackEvent() {
        if (this.Adjust) {
            this.Adjust.trackEvent({ eventToken: "YOUR_EVENT_TOKEN" });
        }
    }
}

Please let me know if you have any further questions

uerceg commented 2 years ago

In case there's still some assistance you need when it comes to this issue, feel free to comment / reopen. Cheers!

yamatsum commented 2 years ago

@YaroslavnaMatkova

I initialized sdk with the following code Then, the following message was displayed. https://github.com/adjust/web_sdk/issues/23#issuecomment-920707406

[adjust-sdk] 2022-06-12T06:39:45.759Z ERROR: You already initiated your instance

Is there any solution?

Also, in the case of typescript, an error occurs in the definition of createContext. How should I define the initial value and type?

yamatsum commented 2 years ago

As a workaround, this is the solution

export const AdjustProvider: FC = ({ children }) => {
  let Adjust;

  const [didInit, setDidInit] = useState(false);

  if (!didInit && typeof window !== "undefined") {
    Adjust = require("@adjustcom/adjust-web-sdk");

    AdjustSdk.initSdk({
      appToken: "YOUR_APP_TOKEN",
      environment: "production", // or "sandbox"
      // ... other optional parameters if needed
    })
    setDidInit(true);
  }

  return (
    <AdjustContext.Provider value={Adjust}>{children}</AdjustContext.Provider>
  );
};