Cache storage works fine in chrome PWA but can't work in IOS pwa - progressive-web-apps

I'm running vue.js app using vite and everything works fine in chrome/firefox browsers. also the basic functionality works fine on safari and IOS. I'm trying to access the cache storage and sync token between pwa app and safari browser.
Two weird things happens:
When user login to the website I set the cache and I simulate user behavior. for example a basic user login and then change the url to something else and back to the website. When this thing happens the caches variable is undefined...
When I set cache and user installs pwa there's no data stored in cache storage.
Note: I persist cache in local-storage of the browser and I know that I can't use local-storage to get token thus I use cache to share it between browser and pwa
Codes
How I cache token:
const CACHE_NAME = "auth";
const TOKEN_KEY = "token";
const FAKE_ENDPOINT = "/get-token";
export const saveToken = async (token: string) => {
try {
const cache = await caches.open(CACHE_NAME);
const responseBody = JSON.stringify({
[TOKEN_KEY]: token
});
const response = new Response(responseBody);
await cache.put(FAKE_ENDPOINT, response);
console.log("Token saved! 🎉");
console.log("Saved token: ", await getToken())
} catch (error) {
// It's up to you how you resolve the error
console.log("saveToken error:", {error});
}
};
export const getToken = async () => {
try {
const cache = await caches.open(CACHE_NAME);
const response = await cache.match(FAKE_ENDPOINT);
if (!response) {
return null;
}
const responseBody = await response.json();
return responseBody[TOKEN_KEY];
} catch (error) {
// Gotta catch 'em all
console.log("getToken error:", {error});
}
};

Related

Flutter - Keycloak integration with openId ( redirecting issue )

I want to authenticate flutter app with Keycloak service via internal webview (without open web browser)
To achieve this objective I used OpenID
When app runs will appear Keycloak login page in internal webview. But when entering the username and password correctly, it redirects to another web page which is as follows.
I guess this case happen due to flutter app cannot handle custom redirections. Does anyone know how to fix this??
My code:
urlLauncher(String url) async {
if (await canLaunch(url)) {
await launchUrlString(url, mode: LaunchMode.inAppWebView);
} else {
print("TOKEN = error");
throw 'Could not launch $url';
}
}
// create an authenticator
var authenticator = new Authenticator(
client,
redirectUri: Uri.parse(_redirectUrl),
scopes: scopes,
urlLancher: urlLauncher,
);
// starts the authentication
var c = await authenticator.authorize();
print("TOKEN = DONE");
// close the webview when finished
await closeInAppWebView();
var res = await c.getTokenResponse();
print("TOKEN = ${res.accessToken}");
pubspec.yaml:
openid_client: ^0.4.6
url_launcher: ^6.1.6
I tried open id , simple auth , flutter app auth also, unfortunately flutter app auth cannot use with internal webview
Future<TokenResponse> authenticate(Uri uri, String clientId,
List<String> scopes, BuildContext context) async {
try {
var issuer = await Issuer.discover(uri);
var client = Client(issuer, clientId);
urlLauncher(String url) async {
Uri uri = Uri.parse(url);
if (await launchUrl(uri)) {
} else {
throw 'Could not launch $url';
}
}
var authenticator = Authenticator(
client,
scopes: scopes,
urlLancher: urlLauncher,
port: 3000,
);
var c = await authenticator.authorize();
await closeInAppWebView();
var res = await c.getTokenResponse();
UserInfo use = await c.getUserInfo();
authUserId(use.subject.toString());
email(use.email.toString());
token(res.accessToken.toString());
logoutUrl = c.generateLogoutUrl();
return res;
} finally {
context.loaderOverlay.hide();
}
}
You can try this out.
Saved the logout URL into a variable cos it'll be needed to logout.

How to handle server side Authentication in nextjs?

I'm setting Token in localstorage and send it to every request with (axios), but the problem is when I use getServerSideProps token is not send because localStorage can't be accessed on server side.
I think I should use Cookies, I tried js-cookies but it didn't work on server as well.
Is there any solution to send token on server side fetching function as getServerSideProps and getStaticProps?
Localstorage is client-side only; use getInitialProps
function Page({ stars }) {
return <div>Next stars: {stars}</div>
}
Page.getInitialProps = async ({ req }) => {
let token;
// server
if (req) return { page: {} };
else {
// client
const token = localStorage.getItem("auth");
const res = await fetch('https://api.github.com/repos/vercel/next.js', { headers: { Authorization: token }});
const data = await res.json();
return { page: data };
}
};
export default Page
Just modify my code, normally it works

Google Auth Page always shown, how to Auth only first time

I am making a calendar app with flutter using googleApi library.
but, When you turn off the app, need to auth again in web site.
i want auth only first time.
is it possible?
// mycode
get _SCOPES => [CalendarApi.CalendarScope];
await clientViaUserConsent(_clientID, _SCOPES, prompt)
.then((AuthClient client) async {
CalendarClient.calendar = CalendarApi(client);
calendarId = await CalendarClient.calendar.calendarList
.list()
.then((value) => value.items[0].id);
});
void saveData(AccessCredentials credentials) {
GetStorage().write(credetialKey, {
"accessTokenData": credentials.accessToken.data,
"accessTokenExpiry": credentials.accessToken.expiry.toString(),
"refreshToken": credentials.refreshToken,
"scopes": credentials.scopes,
"idToken": credentials.idToken
});
}
AccessCredentials getCredetial() {
try {
var map = GetStorage().read(credetialKey);
return AccessCredentials(
AccessToken("Bearer", map["accessTokenData"] as String,
DateTime.parse(map["accessTokenExpiry"])),
map["refreshToken"],
map["scopes"].cast<String>(),
idToken: map["idToken"] as String);
} catch (e) {
return null;
}
}
Client cli = Client();
var c = await refreshCredentials(_clientID, getCredetial(), cli)
.catchError((e) {
print(e);
});
authenticatedClient(cli, c);
error :
DetailedApiRequestError(status: 401, message: Request is missing required authentication credential. Expected OAuth 2 access tok
You can save user session using for example sharedPreferences. Each time the user launch the app your must first check if the session is saved so you can skip the auth process, otherwise you initiate the authentication
i solved it.
save AccessCredentials,
and use autoRefreshingClient;
Client cli = Client();
var c = await refreshCredentials(_clientID, getCredetial(), cli)
.catchError((e) {
print(e);
});
cli = autoRefreshingClient(_clientID, c, cli);

access document.documentElement from puppeteer

I can get access to the entire HTML for any URL by opening dev-tools and typing:
document.documentElement
I am trying to replicate the same behavior using puppeteer, however, the snippet below returns {}
const puppeteer = require('puppeteer'); // v 1.1.0
const iPhone = puppeteer.devices['Pixel 2 XL'];
async function start(canonical_url) {
const browserURL = 'http://127.0.0.1:9222';
const browser = await puppeteer.connect({browserURL});
const page = await browser.newPage();
await page.emulate(iPhone);
await page.goto(canonical_url, {
waitUntil: 'networkidle2',
});
const data = await page.evaluate(() => document.documentElement);
console.log(data);
}
returns:
{}
Any idea on what I could be doing wrong here?

Integration Testing Nodejs/Express/Mongoose with Jest/Supertest One Mongoose Model Saves, One Mongoose Model Doesn't

I use Postman and the ReactJS UI to call this registration execution and it works as I expect. Ironically, the Jest and Supertest integration tests do not produce expected results. When integration testing, the Profile is created and the User is not.
The architecture is pretty simple. MongoDB in a Docker container, and Node using nodemon in VSCode.
I have to be doing something wrong, I just can't spot what it is.
// The Integration Test __test__/users/../user.test.js
const app = require('../../app');
const uuidv4 = require('uuid/v4');
const User = require('../../src/models/User');
const Profile = require('../../src/models/Profile');
const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const request = require("supertest");
const {
MONGO_URI,
TEST_DB_NAME
} = process.env;
let DB_URI = MONGO_URI + TEST_DB_NAME;
let NAME = TEST_DB_NAME;
mongoose.connect(DB_URI, {
useNewUrlParser: true,
useCreateIndex: true,
dbName: NAME
});
describe('User Integration Test', () => {
// make sure app is imported without issues
it('Has App Defined', () => {
expect(app).toBeDefined();
});
let server;
beforeAll(async () => {
// Clear Test Data
await User.deleteMany({});
await Profile.deleteMany({});
server = await app.listen(3001);
});
afterAll(async (done) => {
// Clear Test Data
await User.deleteMany({});
await Profile.deleteMany({});
// Close server
await server.close(done);
});
describe('User route tests', () => {
it('Can Register a User', async () => {
const body = {
"username": "User21",
"email": "user21#user.com",
"password": "123456",
"avatar": "image.jpg"
}
await request(server)
.post('/api/v1/users')
.send(body)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.expect(200)
});
});
// THE EXPRESS ROUTE in api/v1/users.js
const express = require('express');
const auth = require('../../middleware/auth');
const router = express.Router();
const { UserService } = require('../../services');
const {
check,
validationResult
} = require('express-validator/check');
// #route POST api/users
// #desc Register User
// #access Public
// #return status message
router.post('/', [
check('email', 'Please provide a valid email address').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }),
check('username', 'Username is Required.').not().isEmpty()
], async (req, res, next) => {
try {
//--Validate
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const message = await UserService.register(req.body);
return res.status(200).json(message)
} catch (err) {
next(err);
}
});
// THE register METHOD found in ../../services/UserService.js
const register = async (data) => {
try {
// Destructure the data
const {
username,
email,
password,
avatar
} = data;
// remove spaces from username and lcase it
let user_name = username.replace(/\s/g, '').toLowerCase();
// Check if the username or email already exists
await doesUserExist(user_name, email);
// Create a new user
const token = uuidv4();
user = new User({
email: email.toLowerCase(),
username: user_name,
avatar: avatar,
verifyEmailToken: token
});
// encrypt the password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
// Save the user
// (Works Unless Running Jest Integration Tests)
await user.save();
// Create and save an empty Profile for the new user
profile = new Profile();
profile.user = user;
// (Always Works)
await profile.save();
// Send verification email
await send(user, 'Verify Your Email', token, 'verify-email.html');
return { message: 'User was registered successfully.' };
} catch (err) {
throw err;
}
}
// Does user exist method found in ./UserService.js
const doesUserExist = async (username, email) => {
// Check if user exists by email
let message = await checkEmail(email);
if (!message.email_available) {
throw new Error('Email already exists');
}
// Check if user exists by username
message = await checkUserName(username.toLowerCase())
if (!message.username_available) {
throw new Error('Username already exists');
}
return false;
}
When I call this code via the UI, Postman, or curl both the User and Profile are created, as expected.
When I run the Integration Test, npm run test:integration or npm test,
Only the Profile is created.
my package.json scripts:
"test": "jest",
"test:integration": "jest --testPathPattern integration.test",
Finally, no errors are reported anywhere. User simply isn't created.
After a few hours of work and testing this issue I found that the afterAll() event was raise randomly. Sometimes after all the tests in the inner describe() ran and sometimes not. Of course, when afterAll() ran all Users where deleted from the data store.
If moved beforeAll() and AfterAll() to the inner describe(). This worked very well until I included other integration tests, like authorization, for example that also used the User table. I noticed that the test suites are not executed synchronously. As one test suit was blowing out the data in another test suite as the other test suite was executing.
I now set up a database per test suite. Clunky, wonky, hacky and wrong I know but I have to have move on. Does anyone know if you can control the synchronous and/or asynchronous behavior of Jest? Please don't suggest Mocha and/or Chai.
I ran into a similar issue, where there appeared to be an intermittent race condition between seeding a document and then retrieving it.
I fixed it by running jest with the --runInBand flag.