FriendlyCaptcha / friendly-challenge

The widget and docs for the proof of work challenge used in Friendly Captcha. Protect your websites and online services from spam and abuse with Friendly Captcha, a privacy-first anti-bot solution.
https://friendlycaptcha.com
MIT License
414 stars 61 forks source link

[BUG / Question] widget.js is treated as an ES module #163

Closed derHodrig closed 1 year ago

derHodrig commented 1 year ago

Hello, I use nuxtJs version 2.15.3 and friendlycaptcha v0.9.8.

My Component is nothing special, I followed for minimal setup along the HTML Documentation.

I get an error, where I do not really know if its Friendly Captcha or just Nuxt failed to handle the import

The Error require() of ES Module C:\Users\...\node_modules\friendly-challenge\widget.js from C:\Users\...\node_modules\vue-server-renderer\build.dev.js not supported. widget.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules. Instead rename widget.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in C:\Users\...\node_modules\friendly-challenge\package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).
My Component ```vue ```

I also tried the vue guide, but came to the same result. Could you verify that this is related to Friendly Captcha?

Sideinfo

Yes, I use the SSR Mode, but this should not be related to that, since, even if I call this component via <client-only><captcha /></client-only> the error occurs

derHodrig commented 1 year ago

Okey as mentioned here #39 it is not a bug.

With that in mind, i found a solution in the nuxt.config.js

build: {
    transpile: ['friendly-challenge'],
  },

But the journey does not end here.

Next problem comming from ./node_modules/friendly-challenge/node_modules/core-js/modules/esnext.global-this.js

TypeError: $ is not a function

But this is going to be in an other issue

gzuidhof commented 1 year ago

Hi derHodrig,

We ship a few different things you can import, the /widget.js is intended for directly putting into the <head> of your website. What this version does is search your page for mounting points (divs with the frc-captcha class) and attach to those.

In this case it looks like you are looking to use it more in a "library" way, and handling this attachment yourself. You won't have to transpile it. Here is a link to our NextJS example (which I understand is not Nuxt, but hopefully it is useful somewhat anyhow!).

gzuidhof commented 1 year ago

I found this Vue example

<template>
  <div ref="container" class="frc-captcha" :style="{ width: '100%' }"></div>
</template>

<script lang="ts" setup>
import { WidgetInstance } from "friendly-challenge";
import { onUnmounted, ref, watch } from "vue";

const container = ref();
const widget = ref<WidgetInstance>();

// expose reset for a potential template ref
defineExpose({ reset: () => widget.value?.reset() });

const props = defineProps<{
  options: Partial<{
    startMode: "auto" | "focus" | "none";
    solutionFieldName: "frc-captcha-solution";
    sitekey: string;
    readyCallback: () => any;
    startedCallback: () => any;
    doneCallback: (solution: string) => any;
    errorCallback: (error: any) => any;
  }>;
}>();

watch(container, () => {
  if (widget.value) {
    widget.value.reset();
  }

  if (!widget.value && container.value) {
    widget.value = new WidgetInstance(container.value, {
      ...props.options,
    });
  }
});

onUnmounted(() => {
  if (widget.value) {
    widget.value.reset();
  }
});
</script>
derHodrig commented 1 year ago

I adjusted the Component, so it aligns with your example

<template>
  <div
    v-if="
      captchaService.length > 0 &&
      sitekey.length > 0 &&
      captchaService === 'FRCaptchaValidationService'
    "
    ref="captcha"
    class="frc-captcha"
  />
</template>

<script>
import { WidgetInstance } from 'friendly-challenge'
export default {
  name: 'Captcha',
  data() {
    return {
      widget: null,
    }
  },
  computed: {
    sitekey() {
      return this.$store.state.captchaStore.sitekey
    },
    captchaService() {
      return this.$store.state.captchaStore.captchaService
    },
  },
  mounted() {
    if (!this.widget) {
      this.widget = new WidgetInstance(this.$refs.captcha, {
        sitekey: this.sitekey,
        language: 'de',
        puzzleEndpoint: 'https://eu-api.friendlycaptcha.eu/api/v1/puzzle',
        doneCallback: this.onDone,
        errorCallback: this.onError,
      })
    }
  },
  destroyed() {
    if (this.widget) {
      this.widget.destroy()
    }
  },
  methods: {
    onDone(solution) {
      console.log('done', solution)
    },
    onError(error) {
      console.log('error', error)
    },
  },
}
</script>

<style scoped></style>

the $ is not a function issue is still a fact. Seems, it comes from core-js/modules/es.global-this.js

gzuidhof commented 1 year ago

You won't have to transpile anymore I think, have you removed that from the config?

derHodrig commented 1 year ago

Transpiling is still needed. The problem in that point is, it transpiles also the core-js from the friendly-captcha package, I assume. Core-Js is also a dependency of nuxt but friendly-captcha does not cross connect to that one, even if the version is newer.

Thats a hard one. I will look tomorrow for some other ways.

gzuidhof commented 1 year ago

That's strange, it should be importing this file, which is a plain ES module without any dependencies. (You can see an index of the built files here.

derHodrig commented 1 year ago

Hey again,

Finally got it. seems a bit hacky but const { WidgetInstance } = await import('friendly-challenge') in async mounted solved the problem.

I also saw, the container div, should not have any conditional rendering, so I should build separated captcha components, for the specific providers.

Important, no custom plugin or transpile is needed with this solution.

If any other nuxt user needs it, feel free to use it and save time. Thanks for your help @gzuidhof, I really appreciate your fast response and help.

here the new component ```vue ```