pocketbase / js-sdk

PocketBase JavaScript SDK
https://www.npmjs.com/package/pocketbase
MIT License
2.14k stars 127 forks source link

vue.runtime.esm.js?0261:1888 TypeError: _pocketbase.default is not a constructor #10

Closed Er1c0 closed 2 years ago

Er1c0 commented 2 years ago

vue

Er1c0 commented 2 years ago
ganigeorgiev commented 2 years ago

@Er1c0 Hi, could you provide a little more info about your setup and more specifically:

Thanks!

ganigeorgiev commented 2 years ago

I've just created a demo vue project with npm init vue@latest and everything seems to be loading OK.

ganigeorgiev commented 2 years ago

Just created a project with nuxt v2 and v3. Import in nuxt v3 works fine (it is using vite).

But nuxt v2 uses older webpack version and requires explicitly to point to the sdk bundle. Changing your import path to one of the below, should work:

// for execution in browser
import PocketBase from "pocketbase/dist/pocketbase.es"

// or if you are using server-side-rendering
import PocketBase from "pocketbase/dist/pocketbase.cjs"

I'll investigate the issue further and will search if there is a way to avoid this.

mbecker commented 2 years ago

Hi,

I think the error is the same for NextJS.

The setup is as follows:

"next": "12.2.2",
"pocketbase": "^0.1.2",
"react": "18.2.0",
"react-dom": "18.2.0"

node version: v18.2.0

How to reproduce:

npx create-next-app@latest --typescript
# Enter folder
npm install --save pocketbase
# Update file pages/index.ts accordingly
npm run dev

Error is as follows:

Unhandled Runtime Error
TypeError: pocketbase__WEBPACK_IMPORTED_MODULE_3___default(...) is not a constructor

As suggested, using client / ssr is as follows:

// for execution in browser
import PocketBase from "pocketbase/dist/pocketbase.es"
# Error
Server Error
SyntaxError: Unexpected token 'export'

This error happened while generating the page. Any console logs will be displayed in the terminal window.
// or if you are using server-side-rendering
import PocketBase from "pocketbase/dist/pocketbase.cjs"
# Does work with initializing and requesting some data

The SSR import does work. Does this mean that all http requests (from the library) should be done on the server side?

P.s. I'm having some problems to import the types from your typescript definition as well.

ganigeorgiev commented 2 years ago

@mbecker Thank you. I'll try to investigate it and will fix it tomorrow. I may need to rework the distributed bundles and eventually will add umd export.

ganigeorgiev commented 2 years ago

@Er1c0, @mbecker I've just released a v0.2.0 of the SDK that comes with rewritten core, removing all 3rd party commonjs dependencies (axios, qs) and uses only native js primitives that are available in browser, node, demo, react-native, etc.

As a bonus the size was also reduced almost 3.5 times (75kb -> 23kb or ~5kb gzipped!).

The documentation was also updated to reflect the change. To import it in your project it should be enough to do:

// Using ES modules (default)
import PocketBase from 'pocketbase'

// OR if you are using CommonJS modules
const PocketBase = require('pocketbase/cjs')

Since fetch() is supported only in Node17/18+, if you want to use the library with earlier node versions (note that Node16 active support will end somewhere at the end of this year), you will have to import a polifill beforehand. I recommend cross-fetch:

import 'cross-fetch/polyfill';

There are very minor breaking changes related to the error response. Now is normalized and will always return ClientErrorResponse (you could red more in the caveats section - https://github.com/pocketbase/js-sdk#caveats).

If you are still having difficulties loading the SDK, please let me know and I'll investigate it. I've tested with vite, vue-cli generated project, nuxt2, nuxt3, nextjs, node16 + polifill, node 18.

mbecker commented 2 years ago

Hi @ganigeorgiev ,

thanks for your great work! Seems to work. Even with my current node version v16.13.0 and without any polyfill. Does this makes sense?

Anyways, I'm using the library with NextJS and NextAuth.js (and Strava ;-) ). The user data returned from the method "client.Users.authViaOAuth2" has the following TypeScript definitions:

type UserAuthResponse = {
    [key: string]: any;
    token: string;
    user: User;
};

declare class User extends BaseModel {
    email: string;
    verified: boolean;
    lastResetSentAt: string;
    lastVerificationSentAt: string;
    profile: null | Record;
    /**
     * @inheritdoc
     */
    load(data: {
        [key: string]: any;
    }): void;
}

declare class Record extends BaseModel {
    [key: string]: any;
    "@collectionId": string;
    "@collectionName": string;
    "@expand": {
        [key: string]: any;
    };
    /**
     * @inheritdoc
     */
    load(data: {
        [key: string]: any;
    }): void;
}

The internal user's profile structure of PocketBase has the fields "name" and "avatar" which is not included in the definition. Would be nice to include it.

Should I create a new feature request for that?

Additional, the definition for the user's profile "name" and "avatar" are also not explicitly defined in the main project. Plus, the user's profile is not created / updated with "name" and "avatar" by using an OAuth provider. I'm creating an additional feature request in the main repo.

Again, great work!

ganigeorgiev commented 2 years ago

About the polyfill - probably nextjs or the bundler already includes it (practically as long there is something like global.fetch = myFetchPolyfill you are ok).

About the name and avatar fields - there are not part of the user model. In the UserAuthResponse type the OAuth provider profile will be appended as meta object where you'll find the OAuth2 avatar (TS currently allows this because of the [key: string]: any; definition). Since this meta key is currently set only when you are authorized via OAuth2 it is not "fixed" in place, but I'll consider changing that and at least update the definition to be similar to the @expand Record prop, aka. UserAuthResponse could be:

type UserAuthResponse = {
    [key: string]: any;
    token: string,
    user: User,
    meta: {
        [key: string]: any;
    },
};
mbecker commented 2 years ago

Hi, thanks for your feedback. Yes, I was aware of the meta Object. But, I would like to have a more fixed value for the user's profiles field name and avatar. If the user decides to use another OAuth provider the next time, the name and avatar may change. Would be quite confusing ;-)

Again, I think it's a bigger question if the user's profile model should be used as defined the database with the fields name and avatar and if signing up with an OAuth provider for the first time should fill the values from the OAuth provider's information.

The additional meta object in the TypeScript definition looks good! Thanks for that!

ganigeorgiev commented 2 years ago

I think you are mixing the user profile with the OAuth2 authenticated profile.

Each user has a system profile collection attached to them (see the definition of class User). This profile is where name and avatar fields are coming from. From the admin UI, you can define whatever fields you want. The only field that you cannot change is user.profile.userId (because it is marked as system).

Since this profile object is practically a Collection Record with dynamic fields, there is no way to "fix" its definition. You can define your own and cast it if you need to. To update the profile, you could call:

const authResponse = await client.Users.authViaEmail("test@example.com", 123456);

const updatedRecord = await client.Records.update("profiles", authResponse.user.profile.id, {
   name: "example",
   someOtherFieldDefinedInTheAdmin: 123,
   ...
});

The OAuth2 profile is just returned in the response. It is not persisted and it is up to the developer to "transfer" its fields if he/she finds them useful and put them in the PocketBase user's profile.


You can also find some information about the custom profile fields and the profile collection here - https://pocketbase.io/docs/manage-users/#custom-fields

mbecker commented 2 years ago

Hi, thanks again for your feedback and more than detailed answers! Really appreciate your effort!

My understanding at the beginning was that that the sign up process via an OAuth provider creates / updates the user's profile collection with the given name and avatar. In your OAuth provider implementation likes "google" you are explicitly unmarshaling the username and profile pictures; my understanding of your idea was that you want to use the data as the user's profile information in pocketbase (if not, the data from the oauth provider would be quite useless and you are taking only the email address).

Now, I think to understand your design philosophy ;-)

Maybe last question for updating the collection profile with an avatar / image file: I'm now checking at the sign in flow at the client side if the user's collection profile has a field meta.avatarUrl; if yes, I'm downloading it and trying to update the user's profile as follows:

bodyParams["name"] = meta['name'];
const image = await fetch(meta['avatarUrl']);
bodyParams["avatar"] = await image.blob();
await client.Records.update("profiles", user.profile.id, bodyParams, {"enctype": "multipart/form-data"});  

Which file encoding should the image / file has to be saved by pocketbase?

Thanks for your help and again great work!

ganigeorgiev commented 2 years ago

The OAuth profile is not stable and could change from login to login. In addition, as I mentioned, the user profile collection and the name and avatar fields are just common defaults. Developers can change, remove and add new fields from the "Admin UI > Users > Edit profile collection".

About downloading and uploading a remote image in the browser - yes, you can create a image blob but you need to sent it as part of a FormData object. I don't know what {"enctype": "multipart/form-data"} is and why you decided to set it to this value. The 4th argument is for additional query parameters that will be sent with the request.

To upload your file you need to change your bodyParams to be a FormData object and that's it. For example:

const bodyParams = new FormData();
bodyParams.append("name", meta["name"]);
bodyParams.append("avatar", blob);

await client.Records.update("profiles", user.profile.id, bodyParams);  

Alternatively, you could also remove the default avatar profile collection field and add your own avatarUrl text/url field and just store the OAuth2 profile avatar url.


You could also find a file upload example here - https://pocketbase.io/docs/files-handling/#uploading-files

mbecker commented 2 years ago

I'm sorry. With "at client side" I mean the NextJS backend app (nodejs) handling the authentication flow. Sorry for the confusing.

I've managed to work it at the backend app (nodejs) with the npm package form-data as follows:

import FormData from "form-data";
const formData = new FormData();
formData.append("name", meta["name"]);
const image = await fetch(meta['avatarUrl']);
formData.append("avatar", image.body, {
  filename: "large.jpg",
  contentType: "image/jpeg"
});
await client.Records.update("profiles", user.profile.id, formData);  

Maybe it helps anyone. Thank you very much for your help and cheers!