1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-10-13 20:44:23 +02:00

Merge pull request #561 from chinchang/manifestv3

Manifestv3
This commit is contained in:
Kushagra Gour
2024-05-10 17:28:17 +05:30
committed by GitHub
27 changed files with 1582 additions and 987 deletions

View File

@@ -195,6 +195,8 @@ gulp.task('packageExtension', function () {
child_process.execSync('cp src/options.html extension');
child_process.execSync('cp src/eventPage.js extension');
child_process.execSync('cp src/icon-16.png extension');
child_process.execSync('cp offscreen.html extension');
child_process.execSync('cp offscreen.js extension');
child_process.execSync('rm -rf extension/service-worker.js');
return merge(
gulp
@@ -292,10 +294,13 @@ const buildExtension = series(
);
function runWatcher(cb) {
return watch(['src/**/*.js', 'src/**/*.jsx'], function (cbb) {
buildExtension();
cbb();
});
return watch(
['src/**/*.js', 'src/**/*.jsx', 'src/**/*.json'],
function (cbb) {
buildExtension();
cbb();
}
);
cb();
}

2
offscreen.html Normal file
View File

@@ -0,0 +1,2 @@
<!doctype html>
<script src="./offscreen.js"></script>

44
offscreen.js Normal file
View File

@@ -0,0 +1,44 @@
// This URL must point to the public site
const _URL = 'https://webmaker.app/signup';
const iframe = document.createElement('iframe');
iframe.src = _URL;
document.documentElement.appendChild(iframe);
chrome.runtime.onMessage.addListener(handleChromeMessages);
function handleChromeMessages(message, sender, sendResponse) {
// Extensions may have an number of other reasons to send messages, so you
// should filter out any that are not meant for the offscreen document.
if (message.target !== 'offscreen') {
return false;
}
function handleIframeMessage({ data }) {
console.log('got postmessage in offscreen doc from iframe');
try {
if (data.startsWith('!_{')) {
// Other parts of the Firebase library send messages using postMessage.
// You don't care about them in this context, so return early.
return;
}
data = JSON.parse(data);
self.removeEventListener('message', handleIframeMessage);
// being sent back to worker
sendResponse(data);
} catch (e) {
console.log(`json parse failed - ${e.message}`);
}
}
globalThis.addEventListener('message', handleIframeMessage, false);
// Initialize the authentication flow in the iframed document. You must set the
// second argument (targetOrigin) of the message in order for it to be successfully
// delivered.
iframe.contentWindow.postMessage(
{ initAuth: true, providerName: message.providerName },
new URL(_URL).origin
);
return true;
}

1697
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "web-maker",
"version": "6.2.0",
"version": "6.3.0",
"description": "A blazing fast & offline web playground",
"scripts": {
"start": "concurrently --kill-others \"gulp start-preview-server\" \"npm run -s dev\"",
@@ -75,8 +75,8 @@
"code-blast-codemirror": "chinchang/code-blast-codemirror#web-maker",
"codemirror": "^5.65.16",
"copy-webpack-plugin": "^4.5.1",
"firebase": "^10.8.0",
"esprima-next": "^6.0.3",
"firebase": "^8.10.0",
"jszip": "^3.1.5",
"preact": "^10.17.0",
"preact-portal": "^1.1.3",

View File

@@ -1 +1,11 @@
<!doctype html><html><head><title>signInWithPopup</title></head><body> <h1>signInWithPopup</h1> <script type="module" src="/signup/index.1903c1b7.js"></script> </body></html>
<!doctype html>
<html>
<head>
<title>signInWithPopup</title>
</head>
<body>
<h1>signInWithPopup</h1>
<script src="/index.a3cfa626.js" defer=""></script>
</body>
</html>

View File

@@ -1,35 +1,78 @@
import { trackEvent } from './analytics';
import firebase from 'firebase/app';
import { auth } from './firebaseInit';
import { log } from './utils';
import {
GithubAuthProvider as ExtensionGithubAuthProvider,
GoogleAuthProvider as ExtensionGoogleAuthProvider,
signOut,
signInWithCredential
} from 'firebase/auth/web-extension';
import {
signInWithPopup,
GithubAuthProvider,
GoogleAuthProvider
} from 'firebase/auth';
export const auth = {
export const authh = {
logout() {
firebase.auth().signOut();
signOut(auth);
},
login(providerName) {
var provider;
if (providerName === 'facebook') {
provider = new firebase.auth.FacebookAuthProvider();
} else if (providerName === 'twitter') {
provider = new firebase.auth.TwitterAuthProvider();
} else if (providerName === 'google') {
provider = new firebase.auth.GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/userinfo.profile');
} else {
provider = new firebase.auth.GithubAuthProvider();
async login(providerName) {
const onSuccess = () => {
trackEvent(
'fn',
'loggedIn',
providerName,
window.IS_EXTENSION ? 'extension' : 'web'
);
// Save to recommend next time
window.db.local.set({
lastAuthProvider: providerName
});
};
if (window.IS_EXTENSION) {
const result = await chrome.runtime.sendMessage({
type: 'firebase-auth',
providerName
});
if (
result.name === 'FirebaseError' &&
result.code === 'auth/account-exists-with-different-credential'
) {
alert(
'You have already signed up with the same email using different social login'
);
return;
}
let credential;
switch (providerName) {
case 'google':
credential = ExtensionGoogleAuthProvider.credentialFromResult(result);
break;
case 'github':
credential = ExtensionGithubAuthProvider.credentialFromResult(result);
break;
}
// authenticationObject is of the type UserCredentialImpl. Use it to authenticate here
return signInWithCredential(auth, credential).then(onSuccess);
}
return firebase
.auth()
.signInWithPopup(provider)
.then(function() {
trackEvent('fn', 'loggedIn', providerName);
// Save to recommend next time
window.db.local.set({
lastAuthProvider: providerName
});
})
.catch(function(error) {
var provider;
if (providerName === 'google') {
provider = new GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/userinfo.profile');
} else {
provider = new GithubAuthProvider();
}
return signInWithPopup(auth, provider)
.then(onSuccess)
.catch(function (error) {
log(error);
if (error.code === 'auth/account-exists-with-different-credential') {
alert(

View File

@@ -134,7 +134,8 @@ export default class AddLibrary extends Component {
https://cdnjs.cloudflare.com, https://unpkg.com,
https://maxcdn.com, https://cdn77.com,
https://maxcdn.bootstrapcdn.com, https://cdn.jsdelivr.net/,
https://rawgit.com, https://wzrd.in, https://cdn.tailwindcss.com
https://rawgit.com, https://wzrd.in, https://cdn.tailwindcss.com,
https://assets.webmaker.app
</p>
<textarea

View File

@@ -1,6 +1,12 @@
import React, { useState, useEffect } from 'react';
import firebase from 'firebase/app';
import 'firebase/storage';
import {
deleteObject,
uploadBytesResumable,
ref,
listAll,
getDownloadURL
} from 'firebase/storage';
import { storage } from '../firebaseInit';
import { HStack, Stack, VStack } from './Stack';
import { copyToClipboard } from '../utils';
import { Trans } from '@lingui/macro';
@@ -17,6 +23,7 @@ function getFileType(url) {
}
return ext;
}
const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const [files, setFiles] = useState([]);
const [isFetchingFiles, setIsFetchingFiles] = useState(false);
@@ -26,8 +33,6 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const [uploadProgress, setUploadProgress] = useState();
const [listType, setListType] = useState('grid');
const storageRef = firebase.storage().ref(`assets/${window.user?.uid}`);
const uploadFile = file => {
if (file.size > 1024 * 1024) {
// 1MB limit
@@ -39,9 +44,11 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const metadata = {
cacheControl: 'public, max-age=3600' // 1 hr
};
const fileRef = storageRef.child(file.name);
const task = fileRef.put(file, metadata);
const task = uploadBytesResumable(
ref(storage, `assets/${window.user?.uid}/${file.name}`),
file,
metadata
);
task.on(
'state_changed',
@@ -49,12 +56,12 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
// Observe state change events such as progress, pause, and resume
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
// console.log('Upload is ' + progress + '% done');
},
error => {
// Handle unsuccessful uploads
setIsUploading(false);
console.error('File upload error:', error);
// console.log('File upload error:', error);
alertsService.add('⚠️ File upload failed');
},
() => {
@@ -78,11 +85,11 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
// Function to fetch existing files
const fetchFiles = () => {
setIsFetchingFiles(true);
storageRef
.listAll()
listAll(ref(storage, `assets/${window.user?.uid}`))
.then(result => {
const filePromises = result.items.map(item => {
return item.getDownloadURL().then(url => {
return getDownloadURL(item).then(url => {
return { name: item.name, url };
});
});
@@ -190,9 +197,8 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const file = files[index];
const answer = confirm(`Are you sure you want to delete "${file.name}"?`);
if (!answer) return;
const fileRef = storageRef.child(file.name);
fileRef
.delete()
const fileRef = ref(storage, file.url);
deleteObject(fileRef)
.then(() => {
alertsService.add('File deleted successfully');
setFiles(files.filter((_, i) => i !== index));
@@ -259,6 +265,15 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
</div>
</div>
{isFetchingFiles && <LoaderWithText>Fetching files...</LoaderWithText>}
{!isFetchingFiles && !files.length ? (
<Stack justify="center">
<Text align="center" appearance="secondary">
No files uploaded yet
</Text>
</Stack>
) : null}
<VStack align="stretch" gap={1}>
{files.length ? (
<Stack gap={1}>

View File

@@ -106,6 +106,19 @@ export function Changelog(props) {
return (
<div>
<h1>Whats new?</h1>
<Notification version="6.3.0" {...props} isLatest={true}>
<NotificationItem type="bug">
Extension Only: JavaScript is NOW WORKING, again! 🎉 The extension is
now migrated to MV3 which means its much more secure, and of course,
JavaScript now works again! 🥳
</NotificationItem>
<NotificationItem type="ui">
Tailwind CSS 2 template has been removed since Extension now supports
the lateat Tailwind CSS 3 template.
</NotificationItem>
</Notification>
<Notification version="6.2.0" {...props} isLatest={true}>
<li>Preact template updated to latest version with hooks.</li>
<li>Mail option added in help modal.</li>

View File

@@ -38,7 +38,7 @@ export default class ContentWrap extends Component {
window.localStorage.getItem(LocalStorageKeys.WAS_CONSOLE_OPEN) ===
'true',
isCssSettingsModalOpen: false,
isPreviewNotWorkingModalVisible: false,
logs: []
};
@@ -63,15 +63,11 @@ export default class ContentWrap extends Component {
this.clearConsoleBtnClickHandler.bind(this);
this.toggleConsole = this.toggleConsole.bind(this);
this.evalConsoleExpr = this.evalConsoleExpr.bind(this);
this.dismissPreviewNotWorkingModal =
this.dismissPreviewNotWorkingModal.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.state.isConsoleOpen !== nextState.isConsoleOpen ||
this.state.isCssSettingsModalOpen !== nextState.isCssSettingsModalOpen ||
this.state.isPreviewNotWorkingModalVisible !==
nextState.isPreviewNotWorkingModalVisible ||
this.state.logs !== nextState.logs ||
this.state.codeSplitSizes !== nextState.codeSplitSizes ||
this.state.mainSplitSizes !== nextState.mainSplitSizes ||
@@ -145,10 +141,7 @@ export default class ContentWrap extends Component {
createPreviewFile(html, css, js) {
const versionMatch = navigator.userAgent.match(/Chrome\/(\d+)/);
const shouldInlineJs =
!window.webkitRequestFileSystem ||
!window.IS_EXTENSION ||
(versionMatch && +versionMatch[1] >= 104);
const shouldInlineJs = true;
var contents = getCompleteHtml(
html,
css,
@@ -164,80 +157,52 @@ export default class ContentWrap extends Component {
trackEvent.hasTrackedCode = true;
}
if (shouldInlineJs) {
if (this.detachedWindow) {
log('✉️ Sending message to detached window');
this.detachedWindow.postMessage({ contents }, '*');
} else {
// 1. we refresh the frame so that all JS is cleared in the frame. this will
// break the iframe since sandboxed frame isn't served by SW (needed for offline support)
// 2. we cache and remove the sandbox attribute and refresh again so that it gets served by SW
// 3. we add back cached sandbox attr & write the contents to the iframe
const refreshAndDo = fn => {
frameRefreshPromise =
frameRefreshPromise ||
// Race earlier had a settimeout too as a fallback. It was removed because onload
// was firing 100% times.
// TODO: remove race
Promise.race([
new Promise(resolve => {
this.frame.onload = () => {
resolve('onload');
};
})
]);
frameRefreshPromise.then(resolutionReason => {
frameRefreshPromise = null;
log('resolved with ', resolutionReason);
fn();
});
// Setting to blank string cause frame to reload
if (window.IS_EXTENSION) {
this.frame.src = '';
} else {
this.frame.src = this.frame.src;
}
};
const writeInsideIframe = () => {
if (!cachedSandboxAttribute && window.DEBUG) {
// alert('sandbox empty');
}
log('sending PM');
if (window.IS_EXTENSION) {
this.frame.contentDocument.open();
this.frame.contentDocument.write(contents);
this.frame.contentDocument.close();
} else {
this.frame.contentWindow.postMessage({ contents }, '*');
}
};
// refreshAndDo(() => {
// cachedSandboxAttribute = this.frame.getAttribute('sandbox');
// // console.log('removing sandbox', sandbox);
// // this.frame.setAttribute('sweet', sandbox);
// // this.frame.removeAttribute('sandbox');
// refreshAndDo(writeInsideIframe);
// });
refreshAndDo(writeInsideIframe);
}
if (this.detachedWindow) {
log('✉️ Sending message to detached window');
this.detachedWindow.postMessage({ contents }, '*');
} else {
// we need to store user script in external JS file to prevent inline-script
// CSP from affecting it.
writeFile('script.js', blobjs, () => {
writeFile('preview.html', blob, () => {
var origin = chrome.i18n.getMessage()
? `chrome-extension://${chrome.i18n.getMessage('@@extension_id')}`
: `${location.origin}`;
var src = `filesystem:${origin}/temporary/preview.html`;
if (this.detachedWindow) {
this.detachedWindow.postMessage({ url: src }, '*');
} else {
this.frame.src = src;
}
// 1. we refresh the frame so that all JS is cleared in the frame. this will
// break the iframe since sandboxed frame isn't served by SW (needed for offline support)
// 2. we cache and remove the sandbox attribute and refresh again so that it gets served by SW
// 3. we add back cached sandbox attr & write the contents to the iframe
const refreshAndDo = fn => {
frameRefreshPromise =
frameRefreshPromise ||
// Race earlier had a settimeout too as a fallback. It was removed because onload
// was firing 100% times.
// TODO: remove race
Promise.race([
new Promise(resolve => {
this.frame.onload = () => {
resolve('onload');
};
})
]);
frameRefreshPromise.then(resolutionReason => {
frameRefreshPromise = null;
log('resolved with ', resolutionReason);
fn();
});
});
this.frame.src = this.frame.src;
};
const writeInsideIframe = () => {
if (!cachedSandboxAttribute && window.DEBUG) {
// alert('sandbox empty');
}
log('sending PM');
this.frame.contentWindow.postMessage({ contents }, '*');
};
// refreshAndDo(() => {
// cachedSandboxAttribute = this.frame.getAttribute('sandbox');
// // console.log('removing sandbox', sandbox);
// // this.frame.setAttribute('sweet', sandbox);
// // this.frame.removeAttribute('sandbox');
// refreshAndDo(writeInsideIframe);
// });
refreshAndDo(writeInsideIframe);
}
}
cleanupErrors(lang) {
@@ -277,14 +242,13 @@ export default class ContentWrap extends Component {
js: this.cmCodes.js
};
log('🔎 setPreviewContent', isForced);
const targetFrame = this.detachedWindow
? this.detachedWindow //this.detachedWindow.document.querySelector('iframe')
: this.frame;
const targetFrame = this.detachedWindow ? this.detachedWindow : this.frame;
const cssMode = this.props.currentItem.cssMode;
// If just CSS was changed (and everything shudn't be empty),
// change the styles inside the iframe.
if (
false &&
!isForced &&
currentCode.html === this.codeInPreview.html &&
currentCode.js === this.codeInPreview.js &&
@@ -335,19 +299,6 @@ export default class ContentWrap extends Component {
this.showErrors(resultItem.errors.lang, resultItem.errors.data);
}
});
const versionMatch = navigator.userAgent.match(/Chrome\/(\d+)/);
if (
result[2].code &&
versionMatch &&
+versionMatch[1] >= 104 &&
window.IS_EXTENSION &&
!window.sessionStorage.getItem('previewErrorMessageDismissed')
) {
this.setState({
isPreviewNotWorkingModalVisible: true
});
}
});
}
@@ -687,13 +638,6 @@ export default class ContentWrap extends Component {
this.props.onPrettifyBtnClick(codeType);
}
dismissPreviewNotWorkingModal() {
this.setState({
isPreviewNotWorkingModalVisible: false
});
window.sessionStorage.setItem('previewErrorMessageDismissed', true);
}
render() {
// log('contentwrap update');
@@ -960,7 +904,7 @@ export default class ContentWrap extends Component {
ref={el => (this.frame = el)}
frameborder="0"
id="demo-frame"
allowpaymentrequest="true"
src="./indexpm.html"
allowfullscreen="true"
/>
) : (
@@ -996,34 +940,6 @@ export default class ContentWrap extends Component {
settings={this.props.currentItem.cssSettings}
editorTheme={this.props.prefs.editorTheme}
/>
<Modal
show={this.state.isPreviewNotWorkingModalVisible}
closeHandler={this.dismissPreviewNotWorkingModal}
>
<h1>🔴 JavaScript preview not working!</h1>
<p>
Latest version of Chrome has changed a few things because of which
JavaScript preview has stopped working.
</p>
<p>
If you want to work with just HTML & CSS, you can still continue
here. Otherwise, it is recommended to switch to the Web Maker web
app which is much more powerful and works offline too -{' '}
<a href="https://webmaker.app/app" target="_blank">
Go to web app
</a>
</p>
<div class="flex flex-h-end">
<button
class="btn btn--primary"
onClick={this.dismissPreviewNotWorkingModal}
>
Dismiss
</button>
</div>
</Modal>
</div>
</SplitPane>
);

View File

@@ -257,7 +257,7 @@ export default class ContentWrapFiles extends Component {
obj[file.path] =
'<script src="' +
(chrome.extension
? chrome.extension.getURL('/lib/screenlog.js')
? chrome.runtime.getURL('lib/screenlog.js')
: `${location.origin}${
window.DEBUG ? '' : BASE_PATH
}/lib/screenlog.js`) +

View File

@@ -1,12 +1,12 @@
import { h, Component } from 'preact';
import { trackEvent } from '../analytics';
import { auth } from '../auth';
import { authh } from '../auth';
export default class Login extends Component {
login(e) {
const provider = e.target.dataset.authProvider;
trackEvent('ui', 'loginProviderClick', provider);
auth.login(provider);
authh.login(provider);
}
componentDidMount() {
window.db.local.get(

View File

@@ -114,7 +114,7 @@ export function Profile({ user, logoutBtnHandler }) {
? 'Never ever'
: getHumanReadableDate(
currentSubscription.attributes.renews_at
)}
)}
</Text>
</Text>

View File

@@ -9,7 +9,9 @@ import { Icon } from './Icons';
import { Text } from './Text';
const FREE_PUBLIC_ITEM_COUNT = 1;
const BASE_URL = location.origin;
const BASE_URL = location.origin.includes('chrome-extension://')
? 'webmaker.app'
: location.origin;
const TOGGLE_VISIBILITY_API =
/*!window.location.origin.includes('localhost')
? 'http://127.0.0.1:5001/web-maker-app/us-central1/toggleVisibility'

View File

@@ -43,10 +43,10 @@ import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
import { trackEvent } from '../analytics';
import { deferred } from '../deferred';
import { alertsService } from '../notifications';
import firebase from 'firebase/app';
import 'firebase/auth';
import { auth } from '../firebaseInit';
import { onAuthStateChanged } from 'firebase/auth';
import { Profile } from './Profile';
import { auth } from '../auth';
import { authh } from '../auth';
import { SupportDeveloperModal } from './SupportDeveloperModal';
import { KeyboardShortcutsModal } from './KeyboardShortcutsModal';
import { takeScreenshot } from '../takeScreenshot';
@@ -84,7 +84,7 @@ if (module.hot) {
}
const UNSAVED_WARNING_COUNT = 15;
const version = '6.2.0';
const version = '6.3.0';
// Read forced settings as query parameters
window.forcedSettings = {};
@@ -197,7 +197,7 @@ export default class App extends Component {
window.user = savedUser;
}
firebase.auth().onAuthStateChanged(authUser => {
onAuthStateChanged(auth, authUser => {
this.setState({ isLoginModalOpen: false });
if (authUser) {
log('You are -> ', authUser);
@@ -220,7 +220,6 @@ export default class App extends Component {
this.setState({ user: newUser });
window.user = newUser;
// window.localStorage.setItem('user', authUser);
trackEvent('fn', 'loggedIn', window.IS_EXTENSION ? 'extension' : 'web');
if (!window.localStorage[LocalStorageKeys.ASKED_TO_IMPORT_CREATIONS]) {
this.fetchItems(false, true).then(items => {
@@ -641,7 +640,6 @@ export default class App extends Component {
}
componentDidMount() {
console.log('itemId', this.props.itemId);
function setBodySize() {
document.body.style.height = `${window.innerHeight}px`;
}
@@ -1081,18 +1079,15 @@ export default class App extends Component {
alertsService.add('Setting saved');
});
if (window.user) {
window.db.getDb().then(remoteDb => {
remoteDb
.collection('users')
.doc(window.user.uid)
.update({
[`settings.${settingName}`]: this.state.prefs[settingName]
})
.then(arg => {
log(`Setting "${settingName}" for user`, arg);
})
.catch(error => log(error));
});
db.updateUserSetting(
window.user.uid,
settingName,
this.state.prefs[settingName]
)
.then(arg => {
log(`Setting "${settingName}" saved`, arg);
})
.catch(error => log(error));
}
trackEvent('ui', 'updatePref-' + settingName, prefs[settingName]);
}
@@ -1140,7 +1135,7 @@ export default class App extends Component {
}
}
trackEvent('fn', 'loggedOut');
auth.logout();
authh.logout();
this.setState({ isProfileModalOpen: false });
this.createNewItem();
alertsService.add('Log out successfull');
@@ -1943,7 +1938,6 @@ export default class App extends Component {
this.loginBtnClickHandler();
}}
onBuyFromExtensionClick={() => {
console.log('open modal');
this.closeAllOverlays();
this.setState({ isProOnAppModalOpen: true });
}}

137
src/db.js
View File

@@ -1,6 +1,17 @@
import './firebaseInit';
import firebase from 'firebase/app';
// import './firebaseInit';
import 'firebase/firestore';
import { db } from './firebaseInit';
import {
getDoc,
getDocs,
doc,
updateDoc,
setDoc,
where,
collection,
getCountFromServer,
query
} from 'firebase/firestore';
import { deferred } from './deferred';
import { trackEvent } from './analytics';
import { log } from './utils';
@@ -26,9 +37,6 @@ function getArrayFromQuerySnapshot(querySnapshot) {
(() => {
const FAUX_DELAY = 1;
var db;
var dbPromise;
var local = {
get: (obj, cb) => {
const retVal = {};
@@ -64,46 +72,9 @@ function getArrayFromQuerySnapshot(querySnapshot) {
const dbLocalAlias = chrome && chrome.storage ? chrome.storage.local : local;
const dbSyncAlias = chrome && chrome.storage ? chrome.storage.sync : local;
async function getDb() {
if (dbPromise) {
return dbPromise;
}
function getDb() {
log('Initializing firestore');
dbPromise = new Promise((resolve, reject) => {
if (db) {
return resolve(db);
}
const firestoreInstance = firebase.firestore();
return firestoreInstance
.enablePersistence({ experimentalTabSynchronization: true })
.then(function () {
// Initialize Cloud Firestore through firebase
db = firebase.firestore();
// const settings = {
// timestampsInSnapshots: true
// };
// db.settings(settings);
log('firebase db ready', db);
resolve(db);
})
.catch(function (err) {
reject(err.code);
if (err.code === 'failed-precondition') {
// Multiple tabs open, persistence can only be enabled
// in one tab at a a time.
alert(
"Opening Web Maker web app in multiple tabs isn't supported at present and it seems like you already have it opened in another tab. Please use in one tab."
);
trackEvent('fn', 'multiTabError');
} else if (err.code === 'unimplemented') {
// The current browser does not support all of the
// features required to enable persistence
// ...
}
});
});
return dbPromise;
return db;
}
async function getUserLastSeenVersion() {
@@ -134,44 +105,31 @@ function getArrayFromQuerySnapshot(querySnapshot) {
function () {}
);
if (window.user) {
const remoteDb = await getDb();
remoteDb.doc(`users/${window.user.uid}`).update({
updateDoc(doc(db, `users/${window.user.uid}`), {
lastSeenVersion: version
});
}
}
async function getUser(userId) {
const remoteDb = await getDb();
return remoteDb
.doc(`users/${userId}`)
.get()
.then(doc => {
if (!doc.exists) {
// return remoteDb.doc(`users/${userId}`).set(
// {},
// {
// merge: true
// }
// );
return {};
}
const user = doc.data();
return getDoc(doc(db, `users/${userId}`)).then(doc => {
if (!doc.exists()) {
// return setDoc(doc(db, `users/${userId}`), {}, { merge: true });
return {};
}
return user;
});
const user = doc.data();
// Object.assign(window.user, user);
return user;
});
}
async function fetchItem(itemId) {
const remoteDb = await getDb();
return remoteDb
.doc(`items/${itemId}`)
.get()
.then(doc => {
if (!doc.exists) return {};
const data = doc.data();
return data;
});
getDoc(doc(db, `items/${itemId}`)).then(doc => {
if (!doc.exists) return {};
const data = doc.data();
return data;
});
}
// Fetch user settings.
@@ -188,24 +146,28 @@ function getArrayFromQuerySnapshot(querySnapshot) {
}
async function getPublicItemCount(userId) {
const remoteDb = await getDb();
return remoteDb
.collection('items')
.where('createdBy', '==', userId)
.where('isPublic', '==', true)
.get()
.then(snapShot => {
return snapShot.size;
});
const q = query(
collection(db, 'items'),
where('createdBy', '==', userId),
where('isPublic', '==', true)
);
const snapshot = await getCountFromServer(q);
return snapshot.data().count;
}
async function getUserSubscriptionEvents(userId) {
const remoteDb = await getDb();
return remoteDb
.collection('subscriptions')
.where('userId', '==', userId)
.get()
.then(getArrayFromQuerySnapshot);
const q = query(
collection(db, 'subscriptions'),
where('userId', '==', userId)
);
return getDocs(q).then(getArrayFromQuerySnapshot);
}
async function updateUserSetting(userId, settingName, settingValue) {
return updateDoc(doc(db, `users/${userId}`), {
[`settings.${settingName}`]: settingValue
});
}
window.db = {
@@ -217,6 +179,7 @@ function getArrayFromQuerySnapshot(querySnapshot) {
fetchItem,
getPublicItemCount,
getUserSubscriptionEvents,
updateUserSetting,
local: dbLocalAlias,
sync: dbSyncAlias
};

View File

@@ -4,9 +4,7 @@ window.addEventListener('message', e => {
const frame = document.querySelector('iframe');
frame.src = frame.src;
setTimeout(() => {
frame.contentDocument.open();
frame.contentDocument.write(e.data.contents);
frame.contentDocument.close();
frame.contentWindow.postMessage(e.data, '*');
}, 10);
}
if (e.data && e.data.url && e.data.url.match(/index\.html/)) {

View File

@@ -1,16 +1,16 @@
function openApp() {
chrome.tabs.create({
url: chrome.extension.getURL('index.html'),
url: chrome.runtime.getURL('index.html'),
selected: true
});
}
chrome.browserAction.onClicked.addListener(function() {
chrome.action.onClicked.addListener(function () {
openApp();
});
// Listen for tabs getting created.
chrome.tabs.onCreated.addListener(function(tab) {
chrome.tabs.onCreated.addListener(function (tab) {
// If a new tab is opened (without any URL), check user's
// replace Tab setting and act accordingly. Default is false.
if (tab.url === 'chrome://newtab/') {
@@ -18,12 +18,12 @@ chrome.tabs.onCreated.addListener(function(tab) {
{
replaceNewTab: false
},
function(items) {
function (items) {
if (items.replaceNewTab) {
chrome.tabs.update(
tab.id,
{
url: chrome.extension.getURL('index.html')
url: chrome.runtime.getURL('index.html')
},
function callback() {
console.log('ho gaya');
@@ -45,3 +45,78 @@ chrome.runtime.onInstalled.addListener(function callback(details) {
}
}
});
const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html';
// Chrome only allows for a single offscreenDocument. This is a helper function
// that returns a boolean indicating if a document is already active.
async function hasDocument() {
// Check all windows controlled by the service worker to see if one
// of them is the offscreen document with the given path
const matchedClients = await clients.matchAll();
return matchedClients.some(
c => c.url === chrome.runtime.getURL(OFFSCREEN_DOCUMENT_PATH)
);
}
async function setupOffscreenDocument(path) {
// If we do not have a document, we are already setup and can skip
if (await chrome.offscreen.hasDocument()) return;
await chrome.offscreen.createDocument({
url: path,
reasons: [chrome.offscreen.Reason.DOM_SCRAPING],
justification: 'authentication'
});
}
async function closeOffscreenDocument() {
if (!(await hasDocument())) {
return;
}
await chrome.offscreen.closeDocument();
}
function getAuth(providerName) {
return new Promise(async (resolve, reject) => {
// sending to offscreen document
const auth = await chrome.runtime.sendMessage({
type: 'firebase-auth',
target: 'offscreen',
providerName
});
auth?.name !== 'FirebaseError' ? resolve(auth) : reject(auth);
});
}
async function firebaseAuth(providerName) {
await setupOffscreenDocument(OFFSCREEN_DOCUMENT_PATH);
const auth = await getAuth(providerName)
.then(auth => {
console.log('User Authenticated', auth);
return auth;
})
.catch(err => {
if (err.code === 'auth/operation-not-allowed') {
console.error(
'You must enable an OAuth provider in the Firebase' +
' console in order to use signInWithPopup. This sample' +
' uses Google by default.'
);
} else {
console.error(err);
return err;
}
})
.finally(closeOffscreenDocument);
return auth;
}
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
// received from the app
if (message.type === 'firebase-auth') {
firebaseAuth(message.providerName).then(sendResponse);
return true;
}
});

View File

@@ -1,5 +1,12 @@
import firebase from 'firebase/app';
// import 'firebase/firestore';
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import {
initializeFirestore,
persistentLocalCache,
persistentMultipleTabManager
} from 'firebase/firestore';
import { getStorage } from 'firebase/storage';
const config = {
apiKey: 'AIzaSyBl8Dz7ZOE7aP75mipYl2zKdLSRzBU2fFc',
authDomain: 'web-maker-app.firebaseapp.com',
@@ -8,4 +15,14 @@ const config = {
storageBucket: 'web-maker-app.appspot.com',
messagingSenderId: '560473480645'
};
firebase.initializeApp(config);
const app = initializeApp(config);
export { app };
export const auth = getAuth(app);
export const storage = getStorage(app);
export const db = initializeFirestore(app, {
localCache: persistentLocalCache({
tabManager: persistentMultipleTabManager()
})
});

View File

@@ -1,16 +1,27 @@
import { deferred } from './deferred';
import { log } from './utils';
import firebase from 'firebase/app';
import {
collection,
deleteField,
where,
getDoc,
query,
setDoc,
writeBatch,
doc,
deleteDoc,
updateDoc,
getDocs
} from 'firebase/firestore';
export const itemService = {
async getItem(id) {
var remoteDb = await window.db.getDb();
return remoteDb
.doc(`items/${id}`)
.get()
var remoteDb = window.db.getDb();
return getDoc(doc(remoteDb, `items/${id}`))
.then(doc => {
return doc.data();
});
})
.catch(error => log(error));
},
/**
@@ -41,23 +52,23 @@ export const itemService = {
const items = [];
if (window.user && !shouldFetchLocally) {
var remoteDb = await window.db.getDb();
remoteDb
.collection('items')
.where('createdBy', '==', window.user.uid)
.onSnapshot(
function (querySnapshot) {
querySnapshot.forEach(function (doc) {
items.push(doc.data());
});
log('Items fetched in ', Date.now() - t, 'ms');
var remoteDb = window.db.getDb();
d.resolve(items);
},
function () {
d.resolve([]);
}
);
const q = query(
collection(remoteDb, 'items'),
where('createdBy', '==', window.user.uid)
);
getDocs(q)
.then(querySnapshot => {
querySnapshot.forEach(doc => {
items.push(doc.data());
});
log(`${items.length} items fetched in `, Date.now() - t, 'ms');
d.resolve(items);
})
.catch(() => {
d.resolve([]);
});
} else {
let itemIds = await this.getUserItemIds(shouldFetchLocally);
itemIds = Object.getOwnPropertyNames(itemIds || {});
@@ -83,8 +94,8 @@ export const itemService = {
},
async setUser() {
const remoteDb = await window.db.getDb();
return remoteDb.doc(`users/${window.user.uid}`).set({
const remoteDb = window.db.getDb();
return setDoc(doc(remoteDb, `users/${window.user.uid}`), {
items: {}
});
},
@@ -112,16 +123,13 @@ export const itemService = {
}
// LOGGED IN
var remoteDb = await window.db.getDb();
var remoteDb = window.db.getDb();
item.createdBy = window.user.uid;
remoteDb
.collection('items')
.doc(id)
.set(item, {
merge: true
})
.then(arg => {
log('Document written', arg);
setDoc(doc(remoteDb, `items/${id}`), item, {
merge: true
})
.then(() => {
log(`Document written`);
d.resolve();
})
.catch(d.reject);
@@ -161,14 +169,15 @@ export const itemService = {
);
} else {
window.db.getDb().then(remoteDb => {
const batch = remoteDb.batch();
const batch = writeBatch(remoteDb);
/* eslint-disable guard-for-in */
for (var id in items) {
items[id].createdBy = window.user.uid;
batch.set(remoteDb.doc(`items/${id}`), items[id]);
batch.update(remoteDb.doc(`users/${window.user.uid}`), {
batch.set(doc(remoteDb, `items/${id}`), items[id]);
batch.update(doc(remoteDb, `users/${window.user.uid}`), {
[`items.${id}`]: true
});
// Set these items on our cached user object too
window.user.items = window.user.items || {};
window.user.items[id] = true;
@@ -187,14 +196,11 @@ export const itemService = {
window.db.local.remove(id, d.resolve);
return d.promise;
}
const remoteDb = await window.db.getDb();
const remoteDb = window.db.getDb();
log(`Starting to save item ${id}`);
return remoteDb
.collection('items')
.doc(id)
.delete()
.then(arg => {
log('Document removed', arg);
return deleteDoc(doc(remoteDb, `items/${id}`))
.then(() => {
log('Document removed');
})
.catch(error => log(error));
},
@@ -214,13 +220,10 @@ export const itemService = {
}
);
}
const remoteDb = await window.db.getDb();
return remoteDb
.collection('users')
.doc(window.user.uid)
.update({
[`items.${itemId}`]: true
})
const remoteDb = window.db.getDb();
return updateDoc(doc(remoteDb, `users/${window.user.uid}`), {
[`items.${itemId}`]: true
})
.then(arg => {
log(`Item ${itemId} set for user`, arg);
window.user.items = window.user.items || {};
@@ -244,16 +247,13 @@ export const itemService = {
}
);
}
const remoteDb = await window.db.getDb();
return remoteDb
.collection('users')
.doc(window.user.uid)
.update({
[`items.${itemId}`]: firebase.firestore.FieldValue.delete()
})
const remoteDb = window.db.getDb();
return updateDoc(doc(remoteDb, `users/${window.user.uid}`), {
[`items.${itemId}`]: deleteField()
})
.then(arg => {
delete window.user.items[itemId];
log(`Item ${itemId} unset for user`, arg);
delete window.user.items[itemId];
})
.catch(error => log(error));
},

View File

@@ -1,4 +1,4 @@
let mainWindow = window.parent;
let mainWindow = window.parent === window ? window.opener : window.parent;
function sanitizeDomNode(node) {
const fakeNode = {

View File

@@ -1,24 +1,33 @@
{
"name": "Web Maker",
"version": "6.0.0",
"manifest_version": 2,
"version": "6.3.0",
"manifest_version": 3,
"description": "Blazing fast & offline playground for your web experiments",
"homepage_url": "https://webmaker.app",
"permissions": ["storage", "tabs", "<all_urls>"],
"permissions": ["storage", "tabs", "offscreen"],
"optional_permissions": ["downloads"],
"content_security_policy": "script-src 'self' filesystem: http://localhost:* https://localhost:* https://apis.google.com https://ajax.googleapis.com https://code.jquery.com https://cdnjs.cloudflare.com https://unpkg.com https://maxcdn.com https://cdn77.com https://maxcdn.bootstrapcdn.com https://cdn.jsdelivr.net/ https://*.stripe.com/ https://builds.framerjs.com/ https://rawgit.com https://wzrd.in https://www.gstatic.com https://semantic-ui.com https://www.google-analytics.com https://cdn.tailwindcss.com https://www.googletagmanager.com 'unsafe-eval'; object-src 'self'",
"host_permissions": ["<all_urls>"],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'",
"sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' filesystem: http://localhost:* https://localhost:* https://apis.google.com https://ajax.googleapis.com https://code.jquery.com https://cdnjs.cloudflare.com https://unpkg.com https://maxcdn.com https://cdn77.com https://maxcdn.bootstrapcdn.com https://cdn.jsdelivr.net/ https://*.stripe.com/ https://builds.framerjs.com/ https://rawgit.com https://wzrd.in https://www.gstatic.com https://semantic-ui.com https://www.google-analytics.com https://cdn.tailwindcss.com https://www.googletagmanager.com https://firebasestorage.googleapis.com https://assets.webmaker.app 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
},
"options_ui": {
"page": "options.html",
"chrome_style": true
"open_in_tab": false
},
"browser_action": {
"action": {
"default_title": "Start Web Maker",
"default_icon": "icon-16.png"
},
"background": {
"scripts": ["eventPage.js"],
"persistent": false
"sandbox": {
"pages": ["indexpm.html"]
},
"background": {
"service_worker": "eventPage.js",
"type": "module"
},
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp4s9naKH2y92qItJCcj3deRGjjqhDfgUR2WDe34IEYEB4PGtmx/9IO4PaO49gMr82DBRuwxCg/vr9SyiVlG1/j0TApJlkaHVsRZE4EME2rbL1vzIQRE8gNB+b5L6rPl4GPX/eFqIuUe/UgQPZAadLBVxdwBdOv5ou8OY0jrLx/h0wcLVvk9d8/ELpk28Hb2LArCBd+vIngHK55Db1GMEGAyNUVzFCTg/MZ7w5fKLpA94mF2X0/Bv9sCjj7vDir24Mp5Z/Y3obTHvpzddppAE6ZEQ3fmpRkOJ3MbuaKYm9hNHs4az6XLW6Sdlv2YcIcCfvsev/WEKUZeD8KIRtRyyPQIDAQAB",
"icons": {
"16": "icon-16.png",
"48": "icon-48.png"

View File

@@ -35,7 +35,7 @@
<body>
<h3>
Settings
<span style="opacity: 0.6; font-size: 0.7em"> v6.0.0</span>
<span style="opacity: 0.6; font-size: 0.7em"> v6.3.0</span>
</h3>
<form name="optionsForm">
<label>

View File

@@ -12,7 +12,7 @@
<body>
<iframe
src="about://blank"
src="indexpm.html"
frameborder="0"
id="demo-frame"
allowfullscreen

View File

@@ -15,11 +15,6 @@ export default [
title: 'Preact',
img: 'assets/preact-logo.svg'
},
{
id: 'tailwind2',
title: 'Tailwind CSS 2',
img: 'assets/tailwind-logo.svg'
},
{
id: 'tailwind',
title: 'Tailwind CSS 3',

View File

@@ -406,26 +406,26 @@ export function getCompleteHtml(html, css, js, item, isForExport) {
'</head>\n' +
'<body>\n' +
html +
'\n' +
externalJs +
'\n';
if (!isForExport) {
contents +=
'<script src="' +
(chrome.extension
? chrome.extension.getURL('lib/screenlog.js')
? chrome.runtime.getURL('lib/screenlog.js')
: `${location.origin}${
window.DEBUG ? '' : BASE_PATH
}/lib/screenlog.js`) +
'"></script>';
}
contents += '\n' + externalJs;
if (item.jsMode === JsModes.ES6) {
contents +=
'<script src="' +
(chrome.extension
? chrome.extension.getURL('lib/transpilers/babel-polyfill.min.js')
? chrome.runtime.getURL('lib/transpilers/babel-polyfill.min.js')
: `${location.origin}${BASE_PATH}/lib/transpilers/babel-polyfill.min.js`) +
'"></script>';
}
@@ -532,7 +532,7 @@ export function prettify({ file, content, type }) {
}
const worker = new Worker(
chrome.extension
? chrome.extension.getURL('lib/prettier-worker.js')
? chrome.runtime.getURL('lib/prettier-worker.js')
: `${BASE_PATH !== '/' ? BASE_PATH : ''}/lib/prettier-worker.js`
);
worker.postMessage({ content, type });