UNPKG

userdo

Version:

A Durable Object base class that provides user authentication, per-user data storage, and real-time updates for Cloudflare Workers applications.

20 lines (17 loc) 15.3 kB
/*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */var $=function(B,j){return $=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(z,I){z.__proto__=I}||function(z,I){for(var H in I)if(I.hasOwnProperty(H))z[H]=I[H]},$(B,j)};function G(B,j){$(B,j);function z(){this.constructor=B}B.prototype=j===null?Object.create(j):(z.prototype=j.prototype,new z)}function O(B){var j=typeof Symbol==="function"&&B[Symbol.iterator],z=0;if(j)return j.call(B);return{next:function(){if(B&&z>=B.length)B=void 0;return{value:B&&B[z++],done:!B}}}}function A(B,j){var z=typeof Symbol==="function"&&B[Symbol.iterator];if(!z)return B;var I=z.call(B),H,J=[],N;try{while((j===void 0||j-- >0)&&!(H=I.next()).done)J.push(H.value)}catch(X){N={error:X}}finally{try{if(H&&!H.done&&(z=I.return))z.call(I)}finally{if(N)throw N.error}}return J}function C(){for(var B=[],j=0;j<arguments.length;j++)B=B.concat(A(arguments[j]));return B}var M=function(){function B(j,z){this.target=z,this.type=j}return B}(),L=function(B){G(j,B);function j(z,I){var H=B.call(this,"error",I)||this;return H.message=z.message,H.error=z,H}return j}(M),f=function(B){G(j,B);function j(z,I,H){if(z===void 0)z=1000;if(I===void 0)I="";var J=B.call(this,"close",H)||this;return J.wasClean=!0,J.code=z,J.reason=I,J}return j}(M);/*! * Reconnecting WebSocket * by Pedro Ladaria <pedro.ladaria@gmail.com> * https://github.com/pladaria/reconnecting-websocket * License MIT */var F=function(){if(typeof WebSocket!=="undefined")return WebSocket},P=function(B){return typeof B!=="undefined"&&!!B&&B.CLOSING===2},Y={maxReconnectionDelay:1e4,minReconnectionDelay:1000+Math.random()*4000,minUptime:5000,reconnectionDelayGrowFactor:1.3,connectionTimeout:4000,maxRetries:1/0,maxEnqueuedMessages:1/0,startClosed:!1,debug:!1},T=function(){function B(j,z,I){var H=this;if(I===void 0)I={};if(this._listeners={error:[],message:[],open:[],close:[]},this._retryCount=-1,this._shouldReconnect=!0,this._connectLock=!1,this._binaryType="blob",this._closeCalled=!1,this._messageQueue=[],this.onclose=null,this.onerror=null,this.onmessage=null,this.onopen=null,this._handleOpen=function(J){H._debug("open event");var N=H._options.minUptime,X=N===void 0?Y.minUptime:N;if(clearTimeout(H._connectTimeout),H._uptimeTimeout=setTimeout(function(){return H._acceptOpen()},X),H._ws.binaryType=H._binaryType,H._messageQueue.forEach(function(Q){return H._ws.send(Q)}),H._messageQueue=[],H.onopen)H.onopen(J);H._listeners.open.forEach(function(Q){return H._callEventListener(J,Q)})},this._handleMessage=function(J){if(H._debug("message event"),H.onmessage)H.onmessage(J);H._listeners.message.forEach(function(N){return H._callEventListener(J,N)})},this._handleError=function(J){if(H._debug("error event",J.message),H._disconnect(void 0,J.message==="TIMEOUT"?"timeout":void 0),H.onerror)H.onerror(J);H._debug("exec error listeners"),H._listeners.error.forEach(function(N){return H._callEventListener(J,N)}),H._connect()},this._handleClose=function(J){if(H._debug("close event"),H._clearTimeouts(),H._shouldReconnect)H._connect();if(H.onclose)H.onclose(J);H._listeners.close.forEach(function(N){return H._callEventListener(J,N)})},this._url=j,this._protocols=z,this._options=I,this._options.startClosed)this._shouldReconnect=!1;this._connect()}return Object.defineProperty(B,"CONNECTING",{get:function(){return 0},enumerable:!0,configurable:!0}),Object.defineProperty(B,"OPEN",{get:function(){return 1},enumerable:!0,configurable:!0}),Object.defineProperty(B,"CLOSING",{get:function(){return 2},enumerable:!0,configurable:!0}),Object.defineProperty(B,"CLOSED",{get:function(){return 3},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"CONNECTING",{get:function(){return B.CONNECTING},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"OPEN",{get:function(){return B.OPEN},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"CLOSING",{get:function(){return B.CLOSING},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"CLOSED",{get:function(){return B.CLOSED},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"binaryType",{get:function(){return this._ws?this._ws.binaryType:this._binaryType},set:function(j){if(this._binaryType=j,this._ws)this._ws.binaryType=j},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"retryCount",{get:function(){return Math.max(this._retryCount,0)},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"bufferedAmount",{get:function(){var j=this._messageQueue.reduce(function(z,I){if(typeof I==="string")z+=I.length;else if(I instanceof Blob)z+=I.size;else z+=I.byteLength;return z},0);return j+(this._ws?this._ws.bufferedAmount:0)},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"extensions",{get:function(){return this._ws?this._ws.extensions:""},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"protocol",{get:function(){return this._ws?this._ws.protocol:""},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"readyState",{get:function(){if(this._ws)return this._ws.readyState;return this._options.startClosed?B.CLOSED:B.CONNECTING},enumerable:!0,configurable:!0}),Object.defineProperty(B.prototype,"url",{get:function(){return this._ws?this._ws.url:""},enumerable:!0,configurable:!0}),B.prototype.close=function(j,z){if(j===void 0)j=1000;if(this._closeCalled=!0,this._shouldReconnect=!1,this._clearTimeouts(),!this._ws){this._debug("close enqueued: no ws instance");return}if(this._ws.readyState===this.CLOSED){this._debug("close: already closed");return}this._ws.close(j,z)},B.prototype.reconnect=function(j,z){if(this._shouldReconnect=!0,this._closeCalled=!1,this._retryCount=-1,!this._ws||this._ws.readyState===this.CLOSED)this._connect();else this._disconnect(j,z),this._connect()},B.prototype.send=function(j){if(this._ws&&this._ws.readyState===this.OPEN)this._debug("send",j),this._ws.send(j);else{var z=this._options.maxEnqueuedMessages,I=z===void 0?Y.maxEnqueuedMessages:z;if(this._messageQueue.length<I)this._debug("enqueue",j),this._messageQueue.push(j)}},B.prototype.addEventListener=function(j,z){if(this._listeners[j])this._listeners[j].push(z)},B.prototype.dispatchEvent=function(j){var z,I,H=this._listeners[j.type];if(H)try{for(var J=O(H),N=J.next();!N.done;N=J.next()){var X=N.value;this._callEventListener(j,X)}}catch(Q){z={error:Q}}finally{try{if(N&&!N.done&&(I=J.return))I.call(J)}finally{if(z)throw z.error}}return!0},B.prototype.removeEventListener=function(j,z){if(this._listeners[j])this._listeners[j]=this._listeners[j].filter(function(I){return I!==z})},B.prototype._debug=function(){var j=[];for(var z=0;z<arguments.length;z++)j[z]=arguments[z];if(this._options.debug)console.log.apply(console,C(["RWS>"],j))},B.prototype._getNextDelay=function(){var j=this._options,z=j.reconnectionDelayGrowFactor,I=z===void 0?Y.reconnectionDelayGrowFactor:z,H=j.minReconnectionDelay,J=H===void 0?Y.minReconnectionDelay:H,N=j.maxReconnectionDelay,X=N===void 0?Y.maxReconnectionDelay:N,Q=0;if(this._retryCount>0){if(Q=J*Math.pow(I,this._retryCount-1),Q>X)Q=X}return this._debug("next delay",Q),Q},B.prototype._wait=function(){var j=this;return new Promise(function(z){setTimeout(z,j._getNextDelay())})},B.prototype._getNextUrl=function(j){if(typeof j==="string")return Promise.resolve(j);if(typeof j==="function"){var z=j();if(typeof z==="string")return Promise.resolve(z);if(z.then)return z}throw Error("Invalid URL")},B.prototype._connect=function(){var j=this;if(this._connectLock||!this._shouldReconnect)return;this._connectLock=!0;var z=this._options,I=z.maxRetries,H=I===void 0?Y.maxRetries:I,J=z.connectionTimeout,N=J===void 0?Y.connectionTimeout:J,X=z.WebSocket,Q=X===void 0?F():X;if(this._retryCount>=H){this._debug("max retries reached",this._retryCount,">=",H);return}if(this._retryCount++,this._debug("connect",this._retryCount),this._removeListeners(),!P(Q))throw Error("No valid WebSocket class provided");this._wait().then(function(){return j._getNextUrl(j._url)}).then(function(Z){if(j._closeCalled)return;j._debug("connect",{url:Z,protocols:j._protocols}),j._ws=j._protocols?new Q(Z,j._protocols):new Q(Z),j._ws.binaryType=j._binaryType,j._connectLock=!1,j._addListeners(),j._connectTimeout=setTimeout(function(){return j._handleTimeout()},N)})},B.prototype._handleTimeout=function(){this._debug("timeout event"),this._handleError(new L(Error("TIMEOUT"),this))},B.prototype._disconnect=function(j,z){if(j===void 0)j=1000;if(this._clearTimeouts(),!this._ws)return;this._removeListeners();try{this._ws.close(j,z),this._handleClose(new f(j,z,this))}catch(I){}},B.prototype._acceptOpen=function(){this._debug("accept open"),this._retryCount=0},B.prototype._callEventListener=function(j,z){if("handleEvent"in z)z.handleEvent(j);else z(j)},B.prototype._removeListeners=function(){if(!this._ws)return;this._debug("removeListeners"),this._ws.removeEventListener("open",this._handleOpen),this._ws.removeEventListener("close",this._handleClose),this._ws.removeEventListener("message",this._handleMessage),this._ws.removeEventListener("error",this._handleError)},B.prototype._addListeners=function(){if(!this._ws)return;this._debug("addListeners"),this._ws.addEventListener("open",this._handleOpen),this._ws.addEventListener("close",this._handleClose),this._ws.addEventListener("message",this._handleMessage),this._ws.addEventListener("error",this._handleError)},B.prototype._clearTimeouts=function(){clearTimeout(this._connectTimeout),clearTimeout(this._uptimeTimeout)},B}(),V=T;class q{baseUrl;user=null;authListeners=new Set;ws=null;changeListeners=new Map;constructor(B){this.baseUrl=B;this.checkAuthStatus()}get headers(){return{"Content-Type":"application/json"}}async checkAuthStatus(){console.log("\uD83D\uDD0D Checking auth status...");try{let B=`${this.baseUrl}/me`;console.log("\uD83D\uDD0D Fetching auth status from:",B);let j=await fetch(B,{credentials:"include"});if(console.log("\uD83D\uDD0D Auth response status:",j.status),j.ok){let z=await j.json();console.log("\uD83D\uDD0D Auth response data:",z),this.user=z.user}else{let z=await j.text();console.log("\uD83D\uDD0D Auth failed, response:",z),this.user=null}}catch(B){console.error("\uD83D\uDD0D Auth check error:",B),this.user=null}console.log("\uD83D\uDD0D Final auth state:",this.user),this.emitAuthChange()}emitAuthChange(){if(this.authListeners.forEach((B)=>B(this.user)),console.log("\uD83D\uDD10 Auth state changed:",{user:this.user?this.user.email:"none"}),this.user&&!this.ws)console.log("\uD83D\uDD0C Triggering WebSocket connection..."),this.connectWebSocket();else if(!this.user&&this.ws)console.log("\uD83D\uDD0C Disconnecting WebSocket (user logged out)..."),this.disconnectWebSocket()}connectWebSocket(){if(this.ws)return;let j=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}${this.baseUrl}/ws`;console.log("\uD83D\uDD0C Connecting to WebSocket:",j),this.ws=new V(j),this.ws.onopen=()=>{console.log("\uD83D\uDD0C WebSocket connected")},this.ws.onmessage=(z)=>{try{let I=JSON.parse(z.data);this.handleRealtimeMessage(I)}catch(I){console.error("WebSocket message error:",I)}},this.ws.onclose=()=>{console.log("\uD83D\uDD0C WebSocket disconnected")},this.ws.onerror=(z)=>{console.error("WebSocket error:",z)}}disconnectWebSocket(){if(this.ws)this.ws.close(),this.ws=null}handleRealtimeMessage(B){let j=this.changeListeners.get(B.event);if(j)j.forEach((z)=>{try{z(B.data)}catch(I){console.error("Change listener error:",I)}})}onAuthStateChanged(B){this.authListeners.add(B),B(this.user)}offAuthStateChanged(B){this.authListeners.delete(B)}async signup(B,j){let z=await fetch(`${this.baseUrl}/signup`,{method:"POST",headers:this.headers,credentials:"include",body:JSON.stringify({email:B,password:j})});if(!z.ok)throw new Error(await z.text());let I=await z.json();return this.user=I.user,this.emitAuthChange(),I}async login(B,j){let z=await fetch(`${this.baseUrl}/login`,{method:"POST",headers:this.headers,credentials:"include",body:JSON.stringify({email:B,password:j})});if(!z.ok)throw new Error(await z.text());let I=await z.json();return this.user=I.user,this.emitAuthChange(),I}async logout(){await fetch(`${this.baseUrl}/logout`,{method:"POST",headers:this.headers,credentials:"include"}),this.user=null,this.disconnectWebSocket(),this.emitAuthChange()}async get(B){let j=await fetch(`${this.baseUrl.replace("/api","")}/data?key=${encodeURIComponent(B)}`,{headers:this.headers,credentials:"include"});if(!j.ok)throw new Error(await j.text());return(await j.json()).data}async set(B,j){let z=await fetch(`${this.baseUrl.replace("/api","")}/data`,{method:"POST",headers:this.headers,credentials:"include",body:JSON.stringify({key:B,value:j})});if(!z.ok)throw new Error(await z.text());return{ok:!0}}onChange(B,j){let z=`kv:${B}`;if(!this.changeListeners.has(z))this.changeListeners.set(z,new Set);return this.changeListeners.get(z).add(j),console.log(`\uD83D\uDD0C Watching KV key: ${B}`),()=>{let I=this.changeListeners.get(z);if(I){if(I.delete(j),I.size===0)this.changeListeners.delete(z)}console.log(`\uD83D\uDD0C Stopped watching KV key: ${B}`)}}collection(B){let j=`${this.baseUrl}/${B}`,z=this;return{async create(I){let H=await fetch(j,{method:"POST",headers:z.headers,credentials:"include",body:JSON.stringify(I)});if(!H.ok)throw new Error(await H.text());return H.json()},async findById(I){let H=await fetch(`${j}/${I}`,{headers:z.headers,credentials:"include"});if(!H.ok)throw new Error(await H.text());return H.json()},async update(I,H){let J=await fetch(`${j}/${I}`,{method:"PUT",headers:z.headers,credentials:"include",body:JSON.stringify(H)});if(!J.ok)throw new Error(await J.text());return J.json()},async delete(I){await fetch(`${j}/${I}`,{method:"DELETE",headers:z.headers,credentials:"include"})},onChange(I){let H=`table:${B}`;if(!z.changeListeners.has(H))z.changeListeners.set(H,new Set);return z.changeListeners.get(H).add(I),console.log(`\uD83D\uDD0C Watching collection: ${B}`),()=>{let J=z.changeListeners.get(H);if(J){if(J.delete(I),J.size===0)z.changeListeners.delete(H)}console.log(`\uD83D\uDD0C Stopped watching collection: ${B}`)}},query(){let I={};return{where(H,J,N){return I.where=JSON.stringify([H,J,N]),this},orderBy(H,J="asc"){return I.order=`${H}:${J}`,this},limit(H){return I.limit=H,this},async get(){let H=new URLSearchParams(I).toString(),J=await fetch(`${j}?${H}`,{headers:z.headers,credentials:"include"});if(!J.ok)throw new Error(await J.text());return J.json()}}}}}}var D=q;export{D as default,q as UserDOClient};