capacitor-community / sqlite

⚡Capacitor plugin for native & electron SQLite databases.
MIT License
501 stars 119 forks source link

FeatureNotAvailableError after upgrading from Capacitor 3 to Capacitor 4 #324

Closed aanders77 closed 2 years ago

aanders77 commented 2 years ago

I've been using this plugin without problems for several months. But after upgrading from Capacitor 3 to Capacitor 4 yesterday, all sqlite object methods are listed as not available and gives the error message Uncaught (in promise) FeatureNotAvailableError: Feature not available on this platform/device. Check availability before attempting to call this method.

This is my package.json

{
  "name": "fangstdagbok",
  "version": "0.9.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "vue-cli-service test:unit",
    "test:e2e": "vue-cli-service test:e2e",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "@capacitor-community/bluetooth-le": "^2.0.0",
    "@capacitor-community/electron": "^4.1.0",
    "@capacitor-community/http": "^1.4.1",
    "@capacitor-community/sqlite": "^4.1.1",
    "@capacitor/android": "^4.0.0",
    "@capacitor/app": "^4.0.0",
    "@capacitor/core": "^4.0.0",
    "@capacitor/geolocation": "^4.0.0",
    "@capacitor/haptics": "^4.0.0",
    "@capacitor/ios": "^4.0.0",
    "@capacitor/keyboard": "^4.0.0",
    "@capacitor/network": "^4.0.0",
    "@capacitor/status-bar": "^4.0.0",
    "@hapi/hawk": "^8.0.0",
    "@ionic/vue": "^5.4.0",
    "@ionic/vue-router": "^5.4.0",
    "buffer-crc32": "^0.2.13",
    "capacitor-secure-storage-plugin": "^0.8.0",
    "cordova-androidx-build": "^1.0.4",
    "cordova-plugin-device": "^2.1.0",
    "core-js": "^3.6.5",
    "jeep-sqlite": "1.5.8",
    "loadsh": "^0.0.4",
    "maska": "^1.5.0",
    "node-forge": "^1.3.1",
    "ol": "^6.9.0",
    "point-in-polygon": "^1.1.0",
    "selfsigned": "^2.0.1",
    "vue": "^3.2.1",
    "vue-router": "^4.0.0-0",
    "xml-js": "^1.6.11"
  },
  "devDependencies": {
    "@capacitor/cli": "^4.0.0",
    "@vue/cli": "^5.0.4",
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-e2e-cypress": "^5.0.4",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-unit-jest": "~4.5.0",
    "@vue/cli-service": "4.5.17",
    "@vue/compiler-sfc": "^3.0.0-0",
    "@vue/test-utils": "^2.0.0-0",
    "eslint-plugin-vue": "^8.0.3",
    "raw-loader": "^4.0.2",
    "vue-jest": "^5.0.0-0",
    "vue-sqlite-hook": "^3.0.2"
  },
  "description": "An Ionic project"
}

I've tried with the brand new jeep-sqlite 1.6.5 also, but when that didn't work I tried downgrading to 1.5.8 since it was more than just a few days old.

I've run these commands, which I saw you suggested in a different thread.

npm update
npm run build
npx cap sync
npx cap copy web

I've also copied the sql-wasm.wasm file to public/assets afterwards, in case it had changed from the previous one.

Here's the sqlite object as shown in Chrome dev console: image

jepiqueau commented 2 years ago

@aanders77 i do not see the reason for this. look at vue-sqlite-app-starter which is working fine. Have you followed the recommendations from Ionic to migrate your app to Capacitor4 which is what i did:


Install the latest version of the Capacitor CLI to your project using npm i -D @capacitor/cli@latest. Once installed, simply run npx cap migrate to have the CLI handle the migration for you.

my package.json file is as followed

{
  "name": "vue-sqlite-app-starter",
  "version": "4.1.1",
  "description": "Ionic/Vue SQLite Application Starter",
  "author": "Jean Pierre Quéau",
  "license": "MIT",
  "homepage": "./",
  "private": true,
  "scripts": {
    "update": "npm install --save-dev @capacitor-community/sqlite@last --save vue-sqlite-hook@last",
    "serve": "npm run copysqlwasm && vue-cli-service serve",
    "build": "npm run copysqlwasm && vue-cli-service build",
    "test:unit": "vue-cli-service test:unit",
    "test:e2e": "vue-cli-service test:e2e",
    "lint": "vue-cli-service lint",
    "copysqlwasm": "copyfiles -u 3 node_modules/sql.js/dist/sql-wasm.wasm public/assets"
  },
  "dependencies": {
    "@capacitor-community/electron": "^4.1.0",
    "@capacitor-community/sqlite": "^4.1.1",
    "@capacitor/android": "^4.0.0",
    "@capacitor/app": "^4.0.0",
    "@capacitor/core": "^4.0.0",
    "@capacitor/dialog": "^4.0.0",
    "@capacitor/haptics": "^4.0.0",
    "@capacitor/ios": "^4.0.0",
    "@capacitor/keyboard": "^4.0.0",
    "@capacitor/status-bar": "^4.0.0",
    "@ionic/vue": "^6.0.9",
    "@ionic/vue-router": "^6.0.9",
    "core-js": "^3.6.5",
    "vue": "^3.2.31",
    "vue-router": "^4.0.13",
    "vue-sqlite-hook": "^3.0.2"
  },
  "devDependencies": {
    "@capacitor/cli": "^4.0.0",
    "@types/jest": "^27.4.1",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "@vue/cli-plugin-babel": "^5.0.1",
    "@vue/cli-plugin-e2e-cypress": "^5.0.1",
    "@vue/cli-plugin-eslint": "^5.0.1",
    "@vue/cli-plugin-router": "^5.0.1",
    "@vue/cli-plugin-typescript": "^5.0.1",
    "@vue/cli-plugin-unit-jest": "^5.0.1",
    "@vue/cli-service": "^5.0.1",
    "@vue/eslint-config-typescript": "^10.0.0",
    "@vue/test-utils": "^2.0.0-rc.17",
    "@vue/vue3-jest": "^27.0.0-alpha.4",
    "babel-jest": "^27.5.1",
    "copyfiles": "^2.4.1",
    "cypress": "^9.5.1",
    "eslint": "^8.10.0",
    "eslint-plugin-vue": "^8.5.0",
    "jest": "^27.5.1",
    "ts-jest": "^27.1.3",
    "typescript": "4.5.5"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/jepiqueau/vue-sqlite-app-starter.git"
  },
  "bugs": {
    "url": "https://github.com/jepiqueau/vue-sqlite-app-starter.git/issues"
  }
}
aanders77 commented 2 years ago

@jepiqueau Hi, thanks for your quick reply! Yes, that's how I did the migration.

I'll show you how I'm importing and initializing your plugin. Since I'm not using TypeScript, its a little different from your examples. It has been working up until now, though.

main.js

import { Capacitor } from "@capacitor/core";
import { defineCustomElements as jeepSqlite, applyPolyfills } from "jeep-sqlite/loader";
import { CapacitorSQLite, SQLiteConnection } from "@capacitor-community/sqlite";

applyPolyfills().then(() => {
  jeepSqlite(window);
});

const platform = Capacitor.getPlatform();
const sqlite = new SQLiteConnection(CapacitorSQLite);
const app = createApp(App).use(IonicVue).use(router);

app.config.globalProperties.$sqlite = useSQLite();

....

window.addEventListener("DOMContentLoaded", async () => {
  try {
    if (platform === "web") {
      // Create the 'jeep-sqlite' Stencil component
      const jeepSqlite = document.createElement("jeep-sqlite");
      document.body.appendChild(jeepSqlite);
      await customElements.whenDefined("jeep-sqlite");
      // Initialize the Web store
      await sqlite.initWebStore();
      console.log("sqlite",sqlite)
    }
  } catch (err) {
    console.log(`Error: ${err}`);
    throw new Error(`Error: ${err}`);
  }

  router.isReady().then(() => {
    app.mount("#app");
  });
});

File_that_runs_after_main.js:

export async function prepareDB(retry = 3) {
  let sqlite = app.config.globalProperties.$sqlite;
  // If db doesnt yet exist, return undefined
  if (platform === "web" && typeof customElements.get("jeep-sqlite") === "undefined") {
    return undefined;
  }
  if (platform !== "web" && (await sqlite.isDatabase("local_db").result)) {
    return undefined;
  }
  console.log("sqlite", sqlite); // This is the object from my original post

  try {
    const ret = await sqlite.checkConnectionsConsistency();
    const isConn = (await sqlite.isConnection("local_db")).result;
    if (ret.result && isConn) {
      app.config.globalProperties.$db.value = await sqlite.retrieveConnection("local_db");
      await app.config.globalProperties.$db.value.open();
    } else {
      app.config.globalProperties.$db.value = await sqlite.createConnection("local_db");
      await app.config.globalProperties.$db.value.open({ database: "local_db" });
    }
  } catch (error) {
    // Error handling
  }
  return app.config.globalProperties.$db.value;
}
jepiqueau commented 2 years ago

@aanders77 In the main.js i do not see

import { useSQLite} from 'vue-sqlite-hook';
aanders77 commented 2 years ago

@jepiqueau It's there, I just overlooked it when copying the excerpt here. Sorry.

jepiqueau commented 2 years ago

@aanders77 ok you can look at vue-sqlite-app it is with the latest 3.7.0 of capacitor3 not yet 4.

jepiqueau commented 2 years ago

@aanders77 i will stop for to night but i do not see the reason of what happens on your app. by the way

await app.config.globalProperties.$db.value.open({ database: "local_db" });

i think

await app.config.globalProperties.$db.value.open();

should work Good luck

axkristiansen commented 2 years ago

Here is a workaround for this problem. Locate this file: node_modules\vue-sqlite-hook\dist\util\feature-check.js change : export function isFeatureAvailable(plugin, method)
to --> export async function isFeatureAvailable(plugin, method)

alternative hack: return true in any case inside that function as a test

kelchm commented 2 years ago

I'm also seeing this problem after upgrading from capacitor 3 to capacitor 4, but on an Ionic React project.

The project is setup very similarly to react-sqlite-app-starter, so I'm at a bit of a loss why things are working as expected in the sample project.

jepiqueau commented 2 years ago

@aanders77 check if with vue-sqlite-hook@3.0.3 it works you need to update also to @capacitor-community/sqlite@4.1.1 tell me if it works as i cannot test it as i do not get the error @kelchm check if with react-sqlite-hook@3.0.2 it works you need to update also to @capacitor-community/sqlite@4.1.1 tell me if it works as i cannot test it as i do not get the error

aanders77 commented 2 years ago

Thanks @axkristiansen and @jepiqueau. It works now :)

jepiqueau commented 2 years ago

@aanders77 @kelchm Is it now solved on your side if yes can one close the issue

aanders77 commented 2 years ago

@jepiqueau Yes, as I said earlier the issue is solved on our part. Thanks! 😊

alpopa85 commented 2 years ago

Hi there, I'm having the same issue as @kelchm w react-sqlite-hook. I've tried everything, can't get it to work.

Capacitor 4, React 18

Here's my package.json:

{ "name": "121-tablet-2022", "version": "0.0.3", "private": true, "dependencies": { "@capacitor-community/http": "^1.4.1", "@capacitor-community/sqlite": "^4.2.2", "@capacitor/app": "^4.0.0", "@capacitor/core": "^4.0.0", "@capacitor/haptics": "^4.0.0", "@capacitor/ios": "^4.0.0", "@capacitor/keyboard": "^4.0.0", "@capacitor/status-bar": "^4.0.0", "@ionic/react": "^6.1.4", "@ionic/react-router": "^6.0.0", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^12.6.3", "@types/jest": "^26.0.20", "@types/node": "^12.19.15", "@types/react": "^18.0.24", "@types/react-dom": "^18.0.8", "@types/react-router": "^5.1.19", "@types/react-router-dom": "^5.3.3", "axios": "^0.26.1", "date-fns": "^2.28.0", "flux": "^4.0.3", "ionicons": "^5.4.0", "jwt-decode": "^3.1.2", "moment": "^2.29.4", "patch-package": "^6.4.7", "pullstate": "^1.24.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.31.0", "react-moment": "^1.1.2", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-sqlite-hook": "^3.1.1", "tslib": "^2.3.1", "typescript": "^4.1.3", "util": "^0.12.4", "uuidv4": "^6.2.13", "web-vitals": "^0.2.4", "workbox-background-sync": "^5.1.4", "workbox-broadcast-update": "^5.1.4", "workbox-cacheable-response": "^5.1.4", "workbox-core": "^5.1.4", "workbox-expiration": "^5.1.4", "workbox-google-analytics": "^5.1.4", "workbox-navigation-preload": "^5.1.4", "workbox-precaching": "^5.1.4", "workbox-range-requests": "^5.1.4", "workbox-routing": "^5.1.4", "workbox-strategies": "^5.1.4", "workbox-streams": "^5.1.4" }, "scripts": { "update-jeep": "npm install --save @capacitor-community/sqlite@latest --save-dev react-sqlite-hook@latest", "update-capacitor": "npm install --save @capacitor/core@latest --save @capacitor/android@latest --save @capacitor/ios@latest --save-dev @capacitor/cli@latest", "start": "npm run copysqlwasm && react-scripts start", "build": "npm run copysqlwasm && react-scripts build", "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!(@ionic/react|@ionic/react-router|@ionic/core|@stencil/core|ionicons)/)'", "eject": "react-scripts eject", "postinstall": "patch-package", "copysqlwasm": "copyfiles -u 3 node_modules/sql.js/dist/sql-wasm.wasm public/assets" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": {
"@capacitor/cli": "^4.0.0", "@typescript-eslint/eslint-plugin": "^5.42.0", "@typescript-eslint/parser": "^5.42.0", "copyfiles": "^2.4.1", "eslint": "^8.26.0", "eslint-plugin-react": "^7.31.10", "jeep-sqlite": "^1.6.7", "react-scripts": "5.0.1"
}, "description": "An Ionic project" }

jepiqueau commented 2 years ago

@alpopa85 did you look at https://github.com /jepiqueau/react-sqlite-app-starter it has been update to capacitor 4 an react 18.2.0 it works fine

jepiqueau commented 2 years ago

@alpopa85 if you cannot fix it it can you try in the react-sqlite-hook/src/util/feature-check.ts to modify export async function isFeatureAvailable

I thought i did it but it is not. If it works and solves your issue can you please make an issue on the react-sqlite-hook in reference to that one and i will fix it and publish it when i am back home in 2 or 3 weeks time Sorry for the disturbance

alpopa85 commented 2 years ago

Thanks a lot for the reply and suggestion. I'll get back later today w an update after I run some tests.

alpopa85 commented 2 years ago

Alright, so after long digging I found the issue but I have no idea how to fix it.

As soon as I add code making an API call using @capacitor-community/http, the FeatureNotAvailableError is thrown.

This is my code:

if (APP_RUNS_NATIVE) { // HTTP calls for native
        return nativeApiCall(url, postData);        
    } else { // AXIOS calls for browser        
        return axiosApiCall(url, postData);
    }

And the native API call looks like this:

function nativeApiCall(url: string, postData: any) {
    if (postData !== undefined && postData !== null) {
        return Http.post({
            url: url,
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Authorization': `Bearer ${jwtStore.getRawState().token}`
            },
            data: qs.stringify(postData),
            // webFetchExtra: {
                // credentials: "include" // only for cookie authentication / NOT FOR JWT
            // }
        });
    } else {
        return Http.get({
            url: url,
            headers: {'Authorization': `Bearer ${jwtStore.getRawState().token}`},
            // webFetchExtra: {
                // credentials: "include" // only for cookie authentication / NOT FOR JWT
            // },
        });
    }
}

The funny thing is I don't even have to call the nativeApiCall method in order to get the error. As soon as I add the function, the error is thrown. If I comment the function, there is no error! It seems there is something breaking when importing from @capacitor-community/http

Any ideas?

LATER EDIT:

If I transform my function to

function nativeApiCall(url: string, postData: any) {
    if (postData !== undefined && postData !== null) {
       return new Promise(() => { });        
    } else {
        return new Promise(() => { });       
    }
}

the error again dissapears, which shows that the Http from @capacitor-community/http breaks something. Very odd.

LATER LATER EDIT: It seems like I was using a http plugin that is discontinued for Capacitor 4. Since 4.3 there is a http plugin that is available in the @capacitor/core package

import { CapacitorHttp } from '@capacitor/core';

Using this plugin I finally managed to have the sqlite properly initialized. Very odd behavior caused by the http plugin nonetheless...

jepiqueau commented 2 years ago

@alpopa85 thanks to revert to me what a work to finally get there. Did that required to have the async added to the function isFeatureAvailable in the react-sqlite-hook as i suggested or not? I am happy that you finally find the fix good luck for your app development

alpopa85 commented 2 years ago

@jepiqueau There was no need to make the isFeatureAvailable an async function. Everything worked w react-sqlite-hook out of the box. Thanks!

jepiqueau commented 2 years ago

@alpopa85 thanks