Skip to content

Commit 4d90950

Browse files
authored
Experiment with counting views (no displaying yet) (#1047)
1 parent 97ec9b6 commit 4d90950

File tree

3 files changed

+136
-7
lines changed

3 files changed

+136
-7
lines changed

src/components/gui/gui.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import TWRestorePointManager from '../../containers/tw-restore-point-manager.jsx
3737
import TWFontsModal from '../../containers/tw-fonts-modal.jsx';
3838
import TWUnknownPlatformModal from '../../containers/tw-unknown-platform-modal.jsx';
3939
import TWInvalidProjectModal from '../../containers/tw-invalid-project-modal.jsx';
40+
import TWWindChimeSubmitter from '../../containers/tw-windchime-submitter.jsx';
4041

4142
import {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../../lib/layout-constants';
4243
import {resolveStageSize} from '../../lib/screen-utils';
@@ -186,6 +187,7 @@ const GUIComponent = props => {
186187
<React.Fragment>
187188
<TWSecurityManager securityManager={securityManager} />
188189
<TWRestorePointManager />
190+
<TWWindChimeSubmitter isEmbedded={isEmbedded} />
189191
{usernameModalVisible && <TWUsernameModal />}
190192
{settingsModalVisible && <TWSettingsModal />}
191193
{customExtensionModalVisible && <TWCustomExtensionModal />}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React from 'react';
2+
import {connect} from 'react-redux';
3+
import PropTypes from 'prop-types';
4+
import log from '../lib/log';
5+
6+
const ENDPOINT = 'https://windchimes.turbowarp.org/api/chime';
7+
const OPT_OUT_KEY = 'tw:windchime_opt_out';
8+
const submittedThisSession = new Set();
9+
10+
const isOptedOut = () => {
11+
try {
12+
const local = localStorage.getItem(OPT_OUT_KEY);
13+
if (local !== null) {
14+
return local === 'true';
15+
}
16+
} catch (e) {
17+
// ignore
18+
}
19+
20+
// These headers are really intended to be about third-parties so we don't need to follow them,
21+
// but if someone has these set, it's good to assume that they would opt out if given the choice.
22+
// So we'll just respect that preemptively.
23+
return navigator.globalPrivacyControl === 'true' || navigator.doNotTrack === '1';
24+
};
25+
26+
class TWWindchimeSubmitter extends React.Component {
27+
componentDidUpdate (prevProps) {
28+
if (
29+
(this.props.isRunning && !prevProps.isRunning) &&
30+
this.props.projectId
31+
) {
32+
this.submit();
33+
}
34+
}
35+
36+
submit () {
37+
if (isOptedOut() || submittedThisSession.has(this.props.projectId)) {
38+
return;
39+
}
40+
41+
submittedThisSession.add(this.props.projectId);
42+
43+
fetch(ENDPOINT, {
44+
method: 'PUT',
45+
body: JSON.stringify({
46+
resource: `scratch/${this.props.projectId}`,
47+
event: this.props.isEmbedded ? 'view/embed' : 'view/index'
48+
}),
49+
headers: {
50+
'content-type': 'application/json'
51+
}
52+
})
53+
.then(res => {
54+
if (!res.ok) {
55+
log.error('Windchime request got status', res.status);
56+
}
57+
})
58+
.catch(err => {
59+
log.error('Windchime request failed', err);
60+
});
61+
}
62+
63+
render () {
64+
// No visible components.
65+
return null;
66+
}
67+
}
68+
69+
TWWindchimeSubmitter.propTypes = {
70+
isEmbedded: PropTypes.bool.isRequired,
71+
isRunning: PropTypes.bool.isRequired,
72+
projectId: PropTypes.string.isRequired
73+
};
74+
75+
const mapStateToProps = state => ({
76+
isRunning: state.scratchGui.vmStatus.running,
77+
projectId: state.scratchGui.projectState.projectId
78+
});
79+
80+
const mapDispatchToProps = () => ({});
81+
82+
export default connect(
83+
mapStateToProps,
84+
mapDispatchToProps
85+
)(TWWindchimeSubmitter);

static/privacy.html

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,60 @@ <h1>TurboWarp Privacy Policy</h1>
3333

3434
<main>
3535
<!-- UPDATE THIS WHEN MAKING CONTENT CHANGES -->
36-
<p><i>Updated May 24, 2024</i></p>
36+
<p><i>Updated September 27, 2025</i></p>
3737

38-
<p><b>The TurboWarp project respects your privacy.</b> Every website says this, but we mean it: We aren't interested in your data. In the interest of full transparency, this document lists every place where your data might be collected or shared:</p>
38+
<p>The TurboWarp project respects your privacy. We're an open source project ran by volunteers. For transparency, this page lists all the places in TurboWarp where we or a third-party receive your data and how your data is handled.</p>
39+
40+
<h2>Abuse</h2>
41+
<p>When a service is being abused, we may take measures to collect information to protect the service. For example, we could log IP addresses making excessive amounts of requests. We strive to do this carefully so that no one is caught in the crossfire. Data is kept only as long as needed to stop the abuse.</p>
42+
43+
<h2>turbowarp.org</h2>
44+
<p>A randomly generated username may be saved in your browser. You can replace this with a custom username. Randomly generated usernames are anonymized by removing the random numbers before being sent to any cloud variable server.</p>
45+
<p>When you start a project that is loaded from Scratch, this may be logged so that a view counter can be incremented over time. Views are anonymous and can not be tied back to any user. You can prevent your views from being counted by unchecking the box below. This checkbox will not affect your ability to see view counts.</p>
46+
<p>
47+
<label>
48+
<input type="checkbox" class="windchimes-optout">
49+
Allow counting my views
50+
</label>
51+
</p>
52+
<script>
53+
(function() {
54+
const isOptedOut = () => {
55+
try {
56+
const local = localStorage.getItem('tw:windchime_opt_out');
57+
if (local !== null) {
58+
return local === 'true';
59+
}
60+
} catch (e) {
61+
// ignore
62+
}
63+
// These headers are really intended to be about third-parties so we don't need to follow them,
64+
// but if someone has these set, it's good to assume that they would opt out if given the choice.
65+
// So we'll just respect that preemptively.
66+
return navigator.globalPrivacyControl === 'true' || navigator.doNotTrack === '1';
67+
};
68+
const setOptedOut = optedOut => {
69+
try {
70+
localStorage.setItem('tw:windchime_opt_out', optedOut);
71+
} catch (e) {
72+
// ignore
73+
}
74+
};
75+
const checkbox = document.querySelector('.windchimes-optout');
76+
checkbox.checked = !isOptedOut();
77+
checkbox.addEventListener('change', (e) => {
78+
setOptedOut(!e.target.checked);
79+
});
80+
})();
81+
</script>
3982

4083
<h2>Loading projects</h2>
4184
<p>When you load a project from another website, you are subject to the privacy practices of that website. For example, when loading projects from Scratch, you are subject to the <a href="https://scratch.mit.edu/privacy_policy">Scratch privacy policy</a>. When loading projects from Scratch specifically, the project ID will also be shared with TurboWarp as part of Scratch's API does not allow direct access. We may briefly log the project ID for caching.</p>
4285

43-
<h2>Running projects</h2>
44-
<p>When connecting to our cloud variable servers, the project ID and username may be logged for 14 days. Data in cloud variables is sent to anyone else connected at the same time. Custom cloud variable servers are outside of our control.</p>
86+
<h2>Cloud variables</h2>
87+
<p>When connecting to our cloud variable servers, the project ID and username may be logged for 14 days. Data in cloud variables is public and sent to anyone else connected at the same time. Custom cloud variable servers are outside of our control.</p>
88+
89+
<h2>Extensions</h2>
4590
<p>Some extensions must communicate with external APIs to function. For example, the translate and text-to-speech extensions rely on the Scratch API, which is covered by the <a href="https://scratch.mit.edu/privacy_policy">Scratch privacy policy</a>. To improve performance, some extensions access TurboWarp's APIs instead where both the request (for example, the text being translated) and the result after forwarding to Scratch (for example, the translated version of the text) may be cached.</p>
4691
<p>Most extensions on the <a href="https://extensions.turbowarp.org/">official extension gallery</a> will ask for permission when the project attempts to use the extension to access an untrusted website, however we cannot guarantee this is 100% reliable.</p>
4792
<p>When loading a custom extension from a place other than the official gallery such as a URL or file on your computer, the editor will ask for permission to load the extension. If you approve this dialog, the extension can bypass the permission dialogs that official extensions show.</p>
@@ -52,9 +97,6 @@ <h2>Settings</h2>
5297
<h2>Aggregated data</h2>
5398
<p>We may log anonymous aggregated data such as how many people use a certain site per day.</p>
5499

55-
<h2>turbowarp.org</h2>
56-
<p>A randomly generated username may be saved in your browser. You can replace this with a custom username. Randomly generated usernames are anonymized (the random part is removed) before being sent to any cloud variable server.</p>
57-
58100
<h2>TurboWarp Desktop</h2>
59101
<p><a href="https://desktop.turbowarp.org/">TurboWarp Desktop</a> may make requests to check for updates. This can be disabled by pressing "Settings" then "Desktop Settings".</p>
60102
<p><a href="https://desktop.turbowarp.org/">TurboWarp Desktop</a> has a disabled-by-default option to enable "Rich Presence" which shares the name of the project you have open and how long it has been open with chat apps running on your computer, which may be displayed on your public profile. This can be disabled by pressing "Settings" then "Desktop Settings".</p>

0 commit comments

Comments
 (0)