passwordless-id / webauthn

Webauthn / passkeys helper library to make your life easier. Client side, server side and demo included.
https://webauthn.passwordless.id
MIT License
436 stars 51 forks source link

Issue with ES Modules in firebase functions #40

Closed ericaliebmann closed 7 months ago

ericaliebmann commented 7 months ago

Current behavior: Works fine in express in a traditional nodejs app

When deployed to firebase, tried both ts and vanilla js, getting this error. Have tried a number of workaround

src/index.ts:14:27 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@passwordless-id/webauthn")' call instead. To convert this file to an ECMAScript module, change its file extension to '.mts', or add the field "type": "module" to ____unctions/package.json'.

The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@passwordless-id/webauthn")' call instead. To convert this file to an ECMAScript module, change its file extension to '.mts', or add the field "type": "module" to '/_____unctions/package.json'.ts(1479)

Errors

____.min.js:1 Uncaught (in promise) FirebaseError: require() of ES Module /workspace/node_modules/@passwordless-id/webauthn/dist/esm/index.js from /workspace/lib/index.js not supported.

Instead change the require of /workspace/node_modules/@passwordless-id/webauthn/dist/esm/index.js in /workspace/lib/index.js to a dynamic import() which is available in all CommonJS modules.

Also got this one code: 'ERR_REQUIRE_ESM'

dagnelies commented 7 months ago

See https://github.com/passwordless-id/webauthn/issues/35#issuecomment-1900264705

Does that help?

ericaliebmann commented 7 months ago

I just tried that and it did give me a different error so it's a step in the right direction

here is the tsconfig file

{
  "compilerOptions": {
    "module": "NodeNext",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "ESNext",
    "moduleResolution": "NodeNext"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

node_modules/@passwordless-id/webauthn/dist/esm/parsers.d.ts:1:133 - error TS2835: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './types.js'?

1 import { AuthenticatorInfo, ClientInfo, RegistrationEncoded, RegistrationParsed, AuthenticationEncoded, AuthenticationParsed } from './types';


node_modules/long/umd/index.d.ts:1:18 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("../index.js")' call instead.

1 import Long from "../index.js";

Found 2 errors in 2 files.

Errors Files 1 node_modules/@passwordless-id/webauthn/dist/esm/parsers.d.ts:1 1 node_modules/long/umd/index.d.ts:1

Error: functions predeploy error: Command terminated with non-zero exit code 2

ericaliebmann commented 7 months ago

@dagnelies That issue did help, but we had to do some slight changes https://github.com/passwordless-id/webauthn/issues/35#issuecomment-1900264705

3 Changes: Sharing in case you're interested, we needed to modify the build script, update package.json, and create a function which handles dynamic imports

1. Modified build script

// package.json
{
++ 
"type": "module",
"build": "esbuild src/index.ts --platform=node --bundle --target=es2022 --outfile=lib/index.js --external:util --external:cors",
...
}

also updated

// tsconfig.json
{
  "compilerOptions": {
    "module": "NodeNext",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "ESNext",
    "moduleResolution": "NodeNext"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

2. Created function to handle the dynamic import and return the parsedRegistrations

index.ts

// index.js
/**
 * Handles the registration process for a user.
 *
 * @param {RegistrationEncoded} registration - The encoded registration data.
 * @param {string} challenge - The challenge string.
 * @return {RegistrationParsed} The credential object for the registered user.
 */
async function handleRegistration(
  registration: RegistrationEncoded,
  challenge: string,
) {
  // Import the webauthn module
  import("@passwordless-id/webauthn").then(async (module) => {
    const {server} = module;
    console.log("nodejs said, module was loaded successfully");
    const registrationParsed: RegistrationParsed =
      await server.verifyRegistration(registration, {
        challenge: challenge,
        origin: origin,
      });
    return registrationParsed;
  }).catch((error) => {
    // Handle any import errors
    console.log(error);
    return null;
  });
}

3. Return credential

const credential = await handleRegistration(registration, challenge);
return credential;

Now the packages work fine

dagnelies commented 7 months ago

Thanks for the detailed workaround. 👍