Skip to content

Commit

Permalink
Merge pull request #1785 from dexie/dxc-licensing
Browse files Browse the repository at this point in the history
Dexie Cloud licensing support
  • Loading branch information
dfahlander authored Oct 18, 2023
2 parents 27ce021 + 26fe376 commit 879982d
Show file tree
Hide file tree
Showing 49 changed files with 1,139 additions and 669 deletions.
5 changes: 0 additions & 5 deletions addons/dexie-cloud/copydts.sh

This file was deleted.

62 changes: 14 additions & 48 deletions addons/dexie-cloud/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dexie-cloud-addon",
"version": "4.0.1-beta.46",
"version": "4.0.1-beta.48",
"description": "Dexie addon that syncs with to Dexie Cloud",
"main": "dist/umd/dexie-cloud-addon.js",
"type": "module",
Expand Down Expand Up @@ -42,7 +42,7 @@
}
}
},
"types": "dist/types/dexie-cloud-client.d.ts",
"types": "dist/modern/dexie-cloud-client.d.ts",
"engines": {
"node": ">=14"
},
Expand All @@ -53,44 +53,13 @@
"scripts": {
"test": "just-build test && pnpm run test-unit",
"test-unit": "karma start test/unit/karma.conf.js --single-run",
"build": "just-build && bash ./copydts.sh",
"watch": "just-build --watch",
"clean": "rm -rf tools/tmp dist test/unit/bundle.*",
"copydts": "bash ./copydts.sh"
"build": "rollup -c tools/build-configs/rollup.config.mjs",
"watch": "rollup -c tools/build-configs/rollup.config.mjs --watch",
"clean": "rm -rf tools/tmp dist test/unit/bundle.*"
},
"just-build": {
"default": [
"just-build release test"
],
"release": [
"# Build outputs",
"just-build src",
"just-build dexie-cloud service-worker",
"#dts-bundle-generator --external-inlines=dexie-cloud-common --external-imports=rxjs dexie --project src/tsconfig.json -o dist/types/dexie-cloud-addon.d.ts tools/tmp/modern/dexie-cloud-client.d.ts",
"# Minify modern bundle",
"terser --comments false --compress --mangle --module --source-map url=dexie-cloud-addon.min.js.map -o dist/modern/dexie-cloud-addon.min.js -- dist/modern/dexie-cloud-addon.js",
"# Minify umd bundle",
"terser --comments false --compress --mangle --source-map url=dexie-cloud-addon.min.js.map -o dist/umd/dexie-cloud-addon.min.js -- dist/umd/dexie-cloud-addon.js",
"# Minify modern service-worker",
"terser --comments false --compress --mangle --module --source-map url=service-worker.min.js.map -o dist/modern/service-worker.min.js -- dist/modern/service-worker.js",
"# Minify umd service-worker",
"terser --comments false --compress --mangle --source-map url=service-worker.min.js.map -o dist/umd/service-worker.min.js -- dist/umd/service-worker.js"
],
"src": [
"# Build the entire typescript source into modern JS",
"tsc -p src -t es2016 --outDir tools/tmp/modern [--watch 'Watching for file changes.']"
],
"dexie-cloud": [
"# Create a modern bundle in dist/modern",
"rollup -c tools/build-configs/rollup.modern.config.js",
"# Replace {version} and {date} in output files",
"node tools/replaceVersionAndDate.cjs dist/umd/dexie-cloud-addon.js dist/modern/dexie-cloud-addon.js"
],
"service-worker": [
"# Create a modern bundle in dist/modern",
"rollup -c tools/build-configs/rollup.sw.modern.config.js",
"# Replace {version} and {date} in output files",
"node tools/replaceVersionAndDate.cjs dist/umd/service-worker.js dist/modern/service-worker.js"
"rollup -c tools/build-configs/rollup.config.mjs"
],
"test": [
"just-build test-unit"
Expand All @@ -103,29 +72,26 @@
"author": "[email protected]",
"license": "Apache-2.0",
"devDependencies": {
"@rollup/plugin-commonjs": "^18.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
"@types/node": "^18.11.18",
"dreambase-library": "^1.0.19",
"dts-bundle-generator": "^8.0.1",
"dreambase-library": "^1.0.21",
"just-build": "*",
"karma": "*",
"karma-browserstack-launcher": "*",
"karma-chrome-launcher": "*",
"karma-firefox-launcher": "*",
"karma-qunit": "*",
"preact": "*",
"rollup": "^2.45.2",
"rollup-plugin-alias": "*",
"rollup-plugin-commonjs": "*",
"rollup-plugin-dts": "^4.0.1",
"rollup-plugin-node-resolve": "*",
"rollup-plugin-sourcemaps": "*",
"rollup": "^4.1.4",
"terser": "^5.20.0",
"tslib": "*",
"typescript": "^4.9.4"
},
"dependencies": {
"dexie-cloud-common": "^1.0.27",
"dexie-cloud-common": "^1.0.31",
"rxjs": "^7.x"
},
"peerDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions addons/dexie-cloud/src/DexieCloudAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export interface DexieCloudAPI {
grant_type?: 'demo' | 'otp';
}): Promise<void>;

logout(options?: {force?: boolean}): Promise<void>;

/**
* Connect to given URL
*/
Expand Down
3 changes: 3 additions & 0 deletions addons/dexie-cloud/src/DexieCloudOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export interface DexieCloudOptions {
// Disable websocket connection
disableWebSocket?: boolean;

// Disable automatic sync on changes
disableEagerSync?: boolean;

// Provides a custom way of fetching the JWT tokens. This option
// can be used when integrating with custom authentication.
// See https://dexie.org/cloud/docs/db.cloud.configure()#fetchtoken
Expand Down
16 changes: 16 additions & 0 deletions addons/dexie-cloud/src/InvalidLicenseError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export class InvalidLicenseError extends Error {
name = 'InvalidLicenseError';
license?: 'expired' | 'deactivated';
constructor(license?: 'expired' | 'deactivated') {
super(
license === 'expired'
? `License expired`
: license === 'deactivated'
? `User deactivated`
: 'Invalid license'
);
if (license) {
this.license = license;
}
}
}
25 changes: 25 additions & 0 deletions addons/dexie-cloud/src/authentication/TokenErrorResponseError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TokenErrorResponse } from 'dexie-cloud-common';

export class TokenErrorResponseError extends Error {
title: string;
messageCode:
| 'INVALID_OTP'
| 'INVALID_EMAIL'
| 'LICENSE_LIMIT_REACHED'
| 'GENERIC_ERROR';
message: string;
messageParams?: { [param: string]: string };

constructor({
title,
message,
messageCode,
messageParams,
}: TokenErrorResponse) {
super(message);
this.name = 'TokenErrorResponseError';
this.title = title;
this.messageCode = messageCode;
this.messageParams = messageParams;
}
}
90 changes: 76 additions & 14 deletions addons/dexie-cloud/src/authentication/authenticate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Dexie from 'dexie';
import type {
RefreshTokenRequest,
TokenErrorResponse,
TokenFinalResponse,
} from 'dexie-cloud-common';
import { b64encode } from 'dreambase-library/dist/common/base64';
Expand All @@ -11,16 +13,18 @@ import {
DXCMessageAlert,
DXCUserInteraction,
} from '../types/DXCUserInteraction';
import { TokenErrorResponseError } from './TokenErrorResponseError';
import { alertUser, interactWithUser } from './interactWithUser';
import { InvalidLicenseError } from '../InvalidLicenseError';

export type FetchTokenCallback = (tokenParams: {
public_key: string;
hints?: { userId?: string; email?: string; grant_type?: string };
}) => Promise<TokenFinalResponse>;
}) => Promise<TokenFinalResponse | TokenErrorResponse>;

export async function loadAccessToken(
db: DexieCloudDB
): Promise<string | undefined> {
): Promise<UserLogin | null> {
const currentUser = await db.getCurrentUser();
const {
accessToken,
Expand All @@ -29,10 +33,10 @@ export async function loadAccessToken(
refreshTokenExpiration,
claims,
} = currentUser;
if (!accessToken) return;
if (!accessToken) return null;
const expTime = accessTokenExpiration?.getTime() ?? Infinity;
if (expTime > Date.now()) {
return accessToken;
if (expTime > Date.now() && (currentUser.license?.status || 'ok') === 'ok') {
return currentUser;
}
if (!refreshToken) {
throw new Error(`Refresh token missing`);
Expand All @@ -48,8 +52,10 @@ export async function loadAccessToken(
await db.table('$logins').update(claims.sub, {
accessToken: refreshedLogin.accessToken,
accessTokenExpiration: refreshedLogin.accessTokenExpiration,
claims: refreshedLogin.claims,
license: refreshedLogin.license,
});
return refreshedLogin.accessToken;
return refreshedLogin;
}

export async function authenticate(
Expand Down Expand Up @@ -113,11 +119,25 @@ export async function refreshAccessToken(
});
if (res.status !== 200)
throw new Error(`RefreshToken: Status ${res.status} from ${url}/token`);
const response: TokenFinalResponse = await res.json();
const response: TokenFinalResponse | TokenErrorResponse = await res.json();
if (response.type === 'error') {
throw new TokenErrorResponseError(response);
}
login.accessToken = response.accessToken;
login.accessTokenExpiration = response.accessTokenExpiration
? new Date(response.accessTokenExpiration)
: undefined;
login.claims = response.claims;
login.license = {
type: response.userType,
status: response.claims.license || 'ok',
}
if (response.evalDaysLeft != null) {
login.license.evalDaysLeft = response.evalDaysLeft;
}
if (response.userValidUntil != null) {
login.license.validUntil = new Date(response.userValidUntil);
}
return login;
}

Expand Down Expand Up @@ -157,11 +177,20 @@ async function userAuthenticate(
hints,
});

if (response2.type === 'error') {
throw new TokenErrorResponseError(response2);
}

if (response2.type !== 'tokens')
throw new Error(
`Unexpected response type from token endpoint: ${response2.type}`
`Unexpected response type from token endpoint: ${(response2 as any).type}`
);

/*const licenseStatus = response2.claims.license || 'ok';
if (licenseStatus !== 'ok') {
throw new InvalidLicenseError(licenseStatus);
}*/

context.accessToken = response2.accessToken;
context.accessTokenExpiration = new Date(response2.accessTokenExpiration);
context.refreshToken = response2.refreshToken;
Expand All @@ -174,6 +203,16 @@ async function userAuthenticate(
context.email = response2.claims.email;
context.name = response2.claims.name;
context.claims = response2.claims;
context.license = {
type: response2.userType,
status: response2.claims.license || 'ok',
}
if (response2.evalDaysLeft != null) {
context.license.evalDaysLeft = response2.evalDaysLeft;
}
if (response2.userValidUntil != null) {
context.license.validUntil = new Date(response2.userValidUntil);
}

if (response2.alerts && response2.alerts.length > 0) {
await interactWithUser(userInteraction, {
Expand All @@ -185,12 +224,35 @@ async function userAuthenticate(
}
return context;
} catch (error) {
await alertUser(userInteraction, 'Authentication Failed', {
type: 'error',
messageCode: 'GENERIC_ERROR',
message: `We're having a problem authenticating right now.`,
messageParams: {},
}).catch(() => {});
if (error instanceof TokenErrorResponseError) {
await alertUser(userInteraction, error.title, {
type: 'error',
messageCode: error.messageCode,
message: error.message,
messageParams: {},
});
throw error;
}
let message = `We're having a problem authenticating right now.`;
console.error (`Error authenticating`, error);
if (error instanceof TypeError) {
const isOffline = typeof navigator !== undefined && !navigator.onLine;
if (isOffline) {
message = `You seem to be offline. Please connect to the internet and try again.`;
} else if (Dexie.debug || (typeof location !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1'))) {
// The audience is most likely the developer. Suggest to whitelist the localhost origin:
message = `Could not connect to server. Please verify that your origin '${location.origin}' is whitelisted using \`npx dexie-cloud whitelist\``;
} else {
message = `Could not connect to server. Please verify the connection.`;
}
await alertUser(userInteraction, 'Authentication Failed', {
type: 'error',
messageCode: 'GENERIC_ERROR',
message,
messageParams: {},
}).catch(() => {});
}

throw error;
}
}
Expand Down
Loading

0 comments on commit 879982d

Please sign in to comment.