diff --git a/package-lock.json b/package-lock.json
index b769105abd..d6794e2859 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,7 +26,7 @@
"wrangler": "^2.0.23"
},
"engines": {
- "node": "16.x",
+ "node": "18.x",
"npm": ">=7.x"
}
},
@@ -24202,7 +24202,7 @@
},
"packages/api": {
"name": "@web3-storage/api",
- "version": "7.20.0",
+ "version": "7.22.0",
"license": "(Apache-2.0 OR MIT)",
"dependencies": {
"@aws-sdk/client-s3": "^3.53.1",
@@ -27048,7 +27048,7 @@
},
"packages/website": {
"name": "@web3-storage/website",
- "version": "2.37.0",
+ "version": "2.38.2",
"dependencies": {
"@docsearch/react": "^3.0.0",
"@fortawesome/free-brands-svg-icons": "^6.1.2",
diff --git a/package.json b/package.json
index ad2d478481..1724eeec65 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
]
},
"engines": {
- "node": "16.x",
+ "node": "18.x",
"npm": ">=7.x"
}
}
diff --git a/packages/api/src/errors.js b/packages/api/src/errors.js
index 234418d88a..c38c5cc7c3 100644
--- a/packages/api/src/errors.js
+++ b/packages/api/src/errors.js
@@ -193,6 +193,24 @@ export class MaintenanceError extends Error {
}
MaintenanceError.CODE = 'ERROR_MAINTENANCE'
+export class FeatureHasBeenSunsetError extends Error {
+ /**
+ * @param {string} reason
+ */
+ constructor (reason) {
+ super(reason)
+ this.name = 'FeatureHasBeenSunset'
+ /**
+ * The 410 (Gone) status code indicates that access to the target resource
+ * is no longer available at the origin server and that this condition is likely
+ * to be permanent.
+ */
+ this.status = 410 // Gone
+ this.code = FeatureHasBeenSunsetError.CODE
+ }
+}
+FeatureHasBeenSunsetError.CODE = 'ERROR_FEATURE_HAS_BEEN_SUNSET'
+
export class PSAErrorInvalidData extends PinningServiceApiError {
/**
* @param {string} message
diff --git a/packages/api/src/maintenance.js b/packages/api/src/maintenance.js
index af60f47de1..e4425c73fd 100644
--- a/packages/api/src/maintenance.js
+++ b/packages/api/src/maintenance.js
@@ -1,4 +1,4 @@
-import { HTTPError, MaintenanceError } from './errors.js'
+import { FeatureHasBeenSunsetError, HTTPError, MaintenanceError } from './errors.js'
import { getTokenFromRequest } from './auth.js'
/**
@@ -50,10 +50,10 @@ export function withMode (mode) {
* @returns {Response|undefined}
*/
return (request, env, ctx) => {
- const enabled = () => {
- const currentMode = env.MODE
- const currentModeBits = modeBits(currentMode)
+ const currentMode = env.MODE
+ const currentModeBits = modeBits(currentMode)
+ const enabled = () => {
return modeBits(mode).every((bit, i) => {
if (bit === '-') {
return true
@@ -78,6 +78,10 @@ export function withMode (mode) {
// Not enabled, use maintenance handler.
if (!enabled() && !modeSkip()) {
+ const isAfterSunsetStart = env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START ? (env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START < new Date().toISOString()) : false
+ if (isAfterSunsetStart && (currentMode === READ_ONLY)) {
+ throw new FeatureHasBeenSunsetError('This API feature has been sunset, and is no longer available. To continue uploading, use the new web3.storage API: https://web3.storage/docs.')
+ }
return maintenanceHandler()
}
}
diff --git a/packages/api/test/maintenance.spec.js b/packages/api/test/maintenance.spec.js
index 27e0131223..d849165f17 100644
--- a/packages/api/test/maintenance.spec.js
+++ b/packages/api/test/maintenance.spec.js
@@ -62,6 +62,12 @@ describe('maintenance middleware', () => {
assert.throws(() => block(() => { }, {
MODE: NO_READ_OR_WRITE
}), /API undergoing maintenance/)
+
+ // after product sunset, READ_ONLY means FeatureHasBeenSunset
+ assert.throws(() => block(() => { }, {
+ MODE: READ_ONLY,
+ NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START: (new Date(0)).toISOString()
+ }), /FeatureHasBeenSunset/)
})
it('should bypass maintenance mode with a allowed token', async () => {
diff --git a/packages/website/components/w3up-launch.js b/packages/website/components/w3up-launch.js
index 078b22ca36..7e5a304a9c 100644
--- a/packages/website/components/w3up-launch.js
+++ b/packages/website/components/w3up-launch.js
@@ -19,8 +19,8 @@ export const W3upMigrationRecommendationCopy = ({ sunsetStartDate }) => {
const sunsetDateFormatter = new Intl.DateTimeFormat(undefined, { dateStyle: 'long' });
return (
<>
- This web3.storage product will sunset on {sunsetDateFormatter.format(sunsetStartDate)}. We recommend migrating
- your usage of web3.storage to the new web3.storage.
+ This web3.storage product sunset for new uploads on {sunsetDateFormatter.format(sunsetStartDate)}. To continue
+ uploading, migrate to the new web3.storage API.
Click here to create a new account and
here to read about what’s awesome about the new web3.storage experience.