mirror of
https://github.com/heymoosh/ticktick-quick-add-obsidian.git
synced 2026-07-22 05:43:55 +00:00
Enable mobile compatibility with seamless OAuth deep-link
- Flip isDesktopOnly to false in manifest.json; the runtime code uses only platform-safe APIs (requestUrl, crypto.subtle, URLSearchParams). - Register obsidian://ticktick-callback protocol handler so the OAuth redirect returns directly into Obsidian on desktop and mobile, removing the manual copy-paste step. - Verify state parameter against tempState to mitigate CSRF, and clear tempCodeVerifier/tempState after a successful exchange. - Update Vercel callback pages (callback.html, index.html) to redirect to the obsidian:// deep link instead of displaying the code as text. Note: the deployed Vercel page must be redeployed for users to see the new behavior. - Drop the now-vestigial "Authorization code" paste field from the settings UI.
This commit is contained in:
parent
3fae30c62a
commit
23eba63ff7
7 changed files with 54 additions and 37 deletions
|
|
@ -7,19 +7,29 @@
|
|||
<body>
|
||||
<h1>Authorization Complete</h1>
|
||||
<p id="message">Processing...</p>
|
||||
<p id="fallback" style="display:none">
|
||||
If Obsidian did not open automatically,
|
||||
<a id="deeplink" href="#">tap here to return to Obsidian</a>.
|
||||
</p>
|
||||
<script>
|
||||
// Parse query parameters from the URL
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get('code');
|
||||
const state = params.get('state');
|
||||
const messageEl = document.getElementById('message');
|
||||
|
||||
if (code && state) {
|
||||
document.getElementById('message').innerText =
|
||||
`Authorization successful! Your code is: ${code}. Please copy this code and paste it into Obsidian.`;
|
||||
const target = 'obsidian://ticktick-callback'
|
||||
+ '?code=' + encodeURIComponent(code)
|
||||
+ '&state=' + encodeURIComponent(state);
|
||||
messageEl.innerText = 'Returning you to Obsidian…';
|
||||
const fallback = document.getElementById('fallback');
|
||||
const link = document.getElementById('deeplink');
|
||||
link.href = target;
|
||||
fallback.style.display = 'block';
|
||||
window.location.replace(target);
|
||||
} else {
|
||||
document.getElementById('message').innerText =
|
||||
'Authorization failed or was canceled. Please try again.';
|
||||
messageEl.innerText = 'Authorization failed or was canceled. Please try again.';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
22
index.html
22
index.html
|
|
@ -7,19 +7,29 @@
|
|||
<body>
|
||||
<h1>Authorization Complete</h1>
|
||||
<p id="message">Processing...</p>
|
||||
<p id="fallback" style="display:none">
|
||||
If Obsidian did not open automatically,
|
||||
<a id="deeplink" href="#">tap here to return to Obsidian</a>.
|
||||
</p>
|
||||
<script>
|
||||
// Parse query parameters from the URL
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get('code');
|
||||
const state = params.get('state');
|
||||
const messageEl = document.getElementById('message');
|
||||
|
||||
if (code && state) {
|
||||
document.getElementById('message').innerText =
|
||||
`Authorization successful!\nYour code is: ${code}\nPlease copy this code and paste it into Obsidian.`;
|
||||
const target = 'obsidian://ticktick-callback'
|
||||
+ '?code=' + encodeURIComponent(code)
|
||||
+ '&state=' + encodeURIComponent(state);
|
||||
messageEl.innerText = 'Returning you to Obsidian…';
|
||||
const fallback = document.getElementById('fallback');
|
||||
const link = document.getElementById('deeplink');
|
||||
link.href = target;
|
||||
fallback.style.display = 'block';
|
||||
window.location.replace(target);
|
||||
} else {
|
||||
document.getElementById('message').innerText =
|
||||
'Authorization failed or was canceled. Please try again.';
|
||||
messageEl.innerText = 'Authorization failed or was canceled. Please try again.';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
10
main.js
10
main.js
|
|
@ -3,10 +3,8 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
|||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
|
||||
var f=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var U=(r,e)=>{for(var t in e)f(r,t,{get:e[t],enumerable:!0})},A=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of E(e))!_.call(r,s)&&s!==t&&f(r,s,{get:()=>e[s],enumerable:!(n=v(e,s))||n.enumerable});return r};var I=r=>A(f({},"__esModule",{value:!0}),r);var j={};U(j,{default:()=>k});module.exports=I(j);var y=require("obsidian"),o=require("obsidian");var c=require("obsidian"),w={accessToken:"",clientId:"",clientSecret:"",redirectUri:"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",selectionMode:"line",tagPosition:"append"},u=class extends c.PluginSettingTab{constructor(e,t){super(e,t),this.plugin=t}display(){let{containerEl:e}=this;e.empty(),new c.Setting(e).setName("Client id").setDesc("Enter your TickTick Client ID from the Developer Console.").addText(t=>t.setPlaceholder("Your Client ID").setValue(this.plugin.settings.clientId||"").onChange(async n=>{this.plugin.settings.clientId=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Client secret").setDesc("Enter your TickTick Client Secret (input is masked).").addText(t=>(t.inputEl.type="password",t.setPlaceholder("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022").setValue(this.plugin.settings.clientSecret||"").onChange(async n=>{this.plugin.settings.clientSecret=n.trim(),await this.plugin.saveSettings()}),t)),new c.Setting(e).setName("Redirect URI").setDesc("Enter the Redirect URI registered in your TickTick Developer Portal. (Default: https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app)").addText(t=>t.setPlaceholder("https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app").setValue(this.plugin.settings.redirectUri||"").onChange(async n=>{this.plugin.settings.redirectUri=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Selection mode").setDesc("Choose whether the command captures only the current line or the entire paragraph.").addDropdown(t=>t.addOption("line","Current line").addOption("paragraph","Entire paragraph").setValue(this.plugin.settings.selectionMode).onChange(async n=>{this.plugin.settings.selectionMode=n,await this.plugin.saveSettings()})),new c.Setting(e).setName("Tag position").setDesc("Choose whether the #ticktick tag is added to the beginning or end of the text.").addDropdown(t=>t.addOption("append","Append (end)").addOption("prepend","Prepend (beginning)").setValue(this.plugin.settings.tagPosition).onChange(async n=>{this.plugin.settings.tagPosition=n,await this.plugin.saveSettings()})),new c.Setting(e).setName("Access token").setDesc("Your current access token (read-only).").addText(t=>t.setPlaceholder("Access token will appear here").setValue(this.plugin.settings.accessToken||"").onChange(async n=>{this.plugin.settings.accessToken=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Refresh token").setDesc("Stored refresh token (for debugging purposes).").addText(t=>t.setPlaceholder("Refresh token").setValue(this.plugin.settings.refreshToken||"").onChange(async n=>{this.plugin.settings.refreshToken=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Connect to TickTick").setDesc("Click to open the OAuth authorization URL in your browser.").addButton(async t=>{t.setButtonText("Connect").onClick(async()=>{await this.plugin.startAuthFlow()})}),new c.Setting(e).setName("Authorization code").setDesc("Paste the authorization code from TickTick here to obtain an access token.").addText(t=>t.setPlaceholder("Enter authorization code").onChange(async n=>{n.trim()&&(await this.plugin.exchangeAuthCodeForToken(n.trim()),t.setValue(""),this.display())}))}};function m(r){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",t="";for(let n=0;n<r;n++)t+=e.charAt(Math.floor(Math.random()*e.length));return t}function D(r,e){return{text:r.getLine(e),line:e}}function N(r,e){let t=e,n=e,s=r.lineCount();for(;t>0&&r.getLine(t-1).trim()!=="";)t--;for(;n<s-1&&r.getLine(n+1).trim()!=="";)n++;let a="";for(let i=t;i<=n;i++)a+=r.getLine(i)+`
|
||||
`;return{text:a.trim(),start:t,end:n}}async function R(){let r=m(64),t=new TextEncoder().encode(r),n=await crypto.subtle.digest("SHA-256",t),s=(0,y.arrayBufferToBase64)(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");return{codeVerifier:r,codeChallenge:s}}function T(r,e,t){console.log(JSON.stringify({timestamp:new Date().toISOString(),plugin:"ticktick-integration",action:e,...t,clientId:r.settings.clientId?"***":void 0,accessToken:r.settings.accessToken?"***":void 0}))}var k=class extends o.Plugin{async onload(){console.log("Loading TickTick Plugin"),await this.loadSettings(),this.addSettingTab(new u(this.app,this)),this.addCommand({id:"create-ticktick-task",name:"Create TickTick task",editorCallback:async(e,t)=>{let n=e.getCursor();if(!t.file){new o.Notice("No file found for the current view!");return}let s,a,i;if(this.settings.selectionMode==="paragraph"){let{text:h,start:p,end:P}=N(e,n.line);s=h,a=p,i=P}else{let{text:h,line:p}=D(e,n.line);s=h,a=p,i=p}if(!s.trim()){new o.Notice("No text found on the current line!");return}let d=m(8),l=this.settings.tagPosition==="prepend"?`#ticktick ${s} ^${d}`:`${s} #ticktick ^${d}`;e.replaceRange(l,{line:a,ch:0},{line:i,ch:e.getLine(i).length});let g=this.app.vault.getName(),S=t.file.path,x=`obsidian://advanced-uri?vault=${encodeURIComponent(g)}&filepath=${encodeURIComponent(S)}&block=${encodeURIComponent(d)}`,C=`${s}
|
||||
var f=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var A=(r,e)=>{for(var t in e)f(r,t,{get:e[t],enumerable:!0})},I=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of _(e))!E.call(r,s)&&s!==t&&f(r,s,{get:()=>e[s],enumerable:!(n=v(e,s))||n.enumerable});return r};var U=r=>I(f({},"__esModule",{value:!0}),r);var j={};A(j,{default:()=>k});module.exports=U(j);var y=require("obsidian"),o=require("obsidian");var c=require("obsidian"),w={accessToken:"",clientId:"",clientSecret:"",redirectUri:"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",selectionMode:"line",tagPosition:"append"},u=class extends c.PluginSettingTab{constructor(e,t){super(e,t),this.plugin=t}display(){let{containerEl:e}=this;e.empty(),new c.Setting(e).setName("Client id").setDesc("Enter your TickTick Client ID from the Developer Console.").addText(t=>t.setPlaceholder("Your Client ID").setValue(this.plugin.settings.clientId||"").onChange(async n=>{this.plugin.settings.clientId=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Client secret").setDesc("Enter your TickTick Client Secret (input is masked).").addText(t=>(t.inputEl.type="password",t.setPlaceholder("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022").setValue(this.plugin.settings.clientSecret||"").onChange(async n=>{this.plugin.settings.clientSecret=n.trim(),await this.plugin.saveSettings()}),t)),new c.Setting(e).setName("Redirect URI").setDesc("Enter the Redirect URI registered in your TickTick Developer Portal. (Default: https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app)").addText(t=>t.setPlaceholder("https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app").setValue(this.plugin.settings.redirectUri||"").onChange(async n=>{this.plugin.settings.redirectUri=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Selection mode").setDesc("Choose whether the command captures only the current line or the entire paragraph.").addDropdown(t=>t.addOption("line","Current line").addOption("paragraph","Entire paragraph").setValue(this.plugin.settings.selectionMode).onChange(async n=>{this.plugin.settings.selectionMode=n,await this.plugin.saveSettings()})),new c.Setting(e).setName("Tag position").setDesc("Choose whether the #ticktick tag is added to the beginning or end of the text.").addDropdown(t=>t.addOption("append","Append (end)").addOption("prepend","Prepend (beginning)").setValue(this.plugin.settings.tagPosition).onChange(async n=>{this.plugin.settings.tagPosition=n,await this.plugin.saveSettings()})),new c.Setting(e).setName("Access token").setDesc("Your current access token (read-only).").addText(t=>t.setPlaceholder("Access token will appear here").setValue(this.plugin.settings.accessToken||"").onChange(async n=>{this.plugin.settings.accessToken=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Refresh token").setDesc("Stored refresh token (for debugging purposes).").addText(t=>t.setPlaceholder("Refresh token").setValue(this.plugin.settings.refreshToken||"").onChange(async n=>{this.plugin.settings.refreshToken=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Connect to TickTick").setDesc("Opens the OAuth authorization page in your browser. After approving, you will be returned to Obsidian automatically.").addButton(async t=>{t.setButtonText("Connect").onClick(async()=>{await this.plugin.startAuthFlow()})})}};function m(r){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",t="";for(let n=0;n<r;n++)t+=e.charAt(Math.floor(Math.random()*e.length));return t}function D(r,e){return{text:r.getLine(e),line:e}}function N(r,e){let t=e,n=e,s=r.lineCount();for(;t>0&&r.getLine(t-1).trim()!=="";)t--;for(;n<s-1&&r.getLine(n+1).trim()!=="";)n++;let a="";for(let i=t;i<=n;i++)a+=r.getLine(i)+`
|
||||
`;return{text:a.trim(),start:t,end:n}}async function R(){let r=m(64),t=new TextEncoder().encode(r),n=await crypto.subtle.digest("SHA-256",t),s=(0,y.arrayBufferToBase64)(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");return{codeVerifier:r,codeChallenge:s}}function T(r,e,t){console.log(JSON.stringify({timestamp:new Date().toISOString(),plugin:"ticktick-integration",action:e,...t,clientId:r.settings.clientId?"***":void 0,accessToken:r.settings.accessToken?"***":void 0}))}var k=class extends o.Plugin{async onload(){console.log("Loading TickTick Plugin"),await this.loadSettings(),this.addSettingTab(new u(this.app,this)),this.registerObsidianProtocolHandler("ticktick-callback",async e=>{if(!e.code){new o.Notice("TickTick auth failed: no authorization code returned.");return}if(!this.settings.tempState||e.state!==this.settings.tempState){new o.Notice("TickTick auth failed: state mismatch. Please try connecting again.");return}await this.exchangeAuthCodeForToken(e.code)}),this.addCommand({id:"create-ticktick-task",name:"Create TickTick task",editorCallback:async(e,t)=>{let n=e.getCursor();if(!t.file){new o.Notice("No file found for the current view!");return}let s,a,i;if(this.settings.selectionMode==="paragraph"){let{text:g,start:p,end:P}=N(e,n.line);s=g,a=p,i=P}else{let{text:g,line:p}=D(e,n.line);s=g,a=p,i=p}if(!s.trim()){new o.Notice("No text found on the current line!");return}let d=m(8),l=this.settings.tagPosition==="prepend"?`#ticktick ${s} ^${d}`:`${s} #ticktick ^${d}`;e.replaceRange(l,{line:a,ch:0},{line:i,ch:e.getLine(i).length});let h=this.app.vault.getName(),S=t.file.path,b=`obsidian://advanced-uri?vault=${encodeURIComponent(h)}&filepath=${encodeURIComponent(S)}&block=${encodeURIComponent(d)}`,x=`${s}
|
||||
|
||||
[Open in Obsidian](${x})`,b=s.length>50?s.substring(0,50)+"...":s;await this.ensureFreshToken();try{(await this.createTicktickTask(b,C)).success?new o.Notice("TickTick task created successfully!"):new o.Notice("Failed to create TickTick task.")}catch(h){T(this,"task_creation_failed",{error:h.message}),new o.Notice(`Failed to create task: ${h.message}
|
||||
Check console for details.`)}}})}onunload(){console.log("Unloading TickTick Plugin")}async startAuthFlow(){let e=this.settings.clientId;if(!e){new o.Notice("Please enter your Client ID in the settings.");return}let t=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",n=encodeURIComponent("tasks:read tasks:write"),{codeVerifier:s,codeChallenge:a}=await R(),i=m(32);this.settings.tempCodeVerifier=s,this.settings.tempState=i,await this.saveSettings();let d=`https://ticktick.com/oauth/authorize?client_id=${e}&redirect_uri=${encodeURIComponent(t)}&response_type=code&scope=${n}&code_challenge=${a}&code_challenge_method=S256&state=${i}`;window.open(d,"_blank"),new o.Notice("OAuth flow initiated. Please complete it in your browser.")}async exchangeAuthCodeForToken(e){let t="https://ticktick.com/oauth/token",n=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",s=this.settings.clientId,a=this.settings.clientSecret,i=this.settings.tempCodeVerifier;if(!i||!s||!a){new o.Notice("Missing required credentials. Please update your settings.");return}let d=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:n,client_id:s,client_secret:a,code_verifier:i});try{let l=await(0,o.requestUrl)({url:t,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:d.toString()});if(l.status===200){let g=l.json;g.refresh_token?this.settings.refreshToken=g.refresh_token:(console.warn("Refresh token not received during token exchange."),new o.Notice("Refresh token not received. Re-authentication may be required more often.")),this.settings.accessToken=g.access_token,this.settings.tokenExpiry=Date.now()+g.expires_in*1e3*.85,await this.saveSettings(),new o.Notice("TickTick access token obtained successfully!")}else console.error("Token exchange error (status):",l.status),console.log("response.json:",l.json),console.log("response.text:",l.text),new o.Notice("Failed to obtain access token.")}catch(l){console.error("Error during token exchange:",l),new o.Notice("Error during token exchange.")}}async refreshAccessToken(){if(!this.settings.refreshToken)throw new Error("No refresh token available. Please reconnect.");let e="https://ticktick.com/oauth/token",t=this.settings.clientId,n=this.settings.clientSecret,s=new URLSearchParams({grant_type:"refresh_token",refresh_token:this.settings.refreshToken,client_id:t,client_secret:n});try{let a=await(0,o.requestUrl)({url:e,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:s.toString()});if(a.status===200){let i=a.json;i.refresh_token&&(this.settings.refreshToken=i.refresh_token),this.settings.accessToken=i.access_token,this.settings.tokenExpiry=Date.now()+i.expires_in*1e3*.85,await this.saveSettings(),new o.Notice("Access token refreshed successfully!")}else throw console.error("Refresh token error:",a.text),new Error("Failed to refresh token")}catch(a){throw console.error("Error refreshing access token:",a),a}}async ensureFreshToken(){if(this.settings.tokenExpiry&&Date.now()>this.settings.tokenExpiry){T(this,"token_expired",{tokenExpiry:this.settings.tokenExpiry});try{await this.refreshAccessToken()}catch(e){throw new o.Notice("Reauthentication required. Please reconnect to TickTick."),e}}}async createTicktickTask(e,t){let n=this.settings.accessToken;if(!n)return new o.Notice("No access token found. Please connect to TickTick in the settings."),{success:!1};let s="https://api.ticktick.com/open/v1/task",a={title:e,content:t};try{T(this,"task_creation_request",{endpoint:s,title:e});let i=await(0,o.requestUrl)({url:s,method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:JSON.stringify(a)});return i.status===200?{success:!0}:(console.error("TickTick API error (status):",i.status),console.log("response.json:",i.json),console.log("response.text:",i.text),{success:!1})}catch(i){return console.error("TickTick API error:",i),{success:!1}}}async loadSettings(){this.settings=Object.assign({},w,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}};
|
||||
|
||||
/* nosourcemap */
|
||||
[Open in Obsidian](${b})`,C=s.length>50?s.substring(0,50)+"...":s;await this.ensureFreshToken();try{(await this.createTicktickTask(C,x)).success?new o.Notice("TickTick task created successfully!"):new o.Notice("Failed to create TickTick task.")}catch(g){T(this,"task_creation_failed",{error:g.message}),new o.Notice(`Failed to create task: ${g.message}
|
||||
Check console for details.`)}}})}onunload(){console.log("Unloading TickTick Plugin")}async startAuthFlow(){let e=this.settings.clientId;if(!e){new o.Notice("Please enter your Client ID in the settings.");return}let t=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",n=encodeURIComponent("tasks:read tasks:write"),{codeVerifier:s,codeChallenge:a}=await R(),i=m(32);this.settings.tempCodeVerifier=s,this.settings.tempState=i,await this.saveSettings();let d=`https://ticktick.com/oauth/authorize?client_id=${e}&redirect_uri=${encodeURIComponent(t)}&response_type=code&scope=${n}&code_challenge=${a}&code_challenge_method=S256&state=${i}`;window.open(d,"_blank"),new o.Notice("OAuth flow initiated. Please complete it in your browser.")}async exchangeAuthCodeForToken(e){let t="https://ticktick.com/oauth/token",n=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",s=this.settings.clientId,a=this.settings.clientSecret,i=this.settings.tempCodeVerifier;if(!i||!s||!a){new o.Notice("Missing required credentials. Please update your settings.");return}let d=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:n,client_id:s,client_secret:a,code_verifier:i});try{let l=await(0,o.requestUrl)({url:t,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:d.toString()});if(l.status===200){let h=l.json;h.refresh_token?this.settings.refreshToken=h.refresh_token:(console.warn("Refresh token not received during token exchange."),new o.Notice("Refresh token not received. Re-authentication may be required more often.")),this.settings.accessToken=h.access_token,this.settings.tokenExpiry=Date.now()+h.expires_in*1e3*.85,this.settings.tempCodeVerifier=void 0,this.settings.tempState=void 0,await this.saveSettings(),new o.Notice("TickTick access token obtained successfully!")}else console.error("Token exchange error (status):",l.status),console.log("response.json:",l.json),console.log("response.text:",l.text),new o.Notice("Failed to obtain access token.")}catch(l){console.error("Error during token exchange:",l),new o.Notice("Error during token exchange.")}}async refreshAccessToken(){if(!this.settings.refreshToken)throw new Error("No refresh token available. Please reconnect.");let e="https://ticktick.com/oauth/token",t=this.settings.clientId,n=this.settings.clientSecret,s=new URLSearchParams({grant_type:"refresh_token",refresh_token:this.settings.refreshToken,client_id:t,client_secret:n});try{let a=await(0,o.requestUrl)({url:e,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:s.toString()});if(a.status===200){let i=a.json;i.refresh_token&&(this.settings.refreshToken=i.refresh_token),this.settings.accessToken=i.access_token,this.settings.tokenExpiry=Date.now()+i.expires_in*1e3*.85,await this.saveSettings(),new o.Notice("Access token refreshed successfully!")}else throw console.error("Refresh token error:",a.text),new Error("Failed to refresh token")}catch(a){throw console.error("Error refreshing access token:",a),a}}async ensureFreshToken(){if(this.settings.tokenExpiry&&Date.now()>this.settings.tokenExpiry){T(this,"token_expired",{tokenExpiry:this.settings.tokenExpiry});try{await this.refreshAccessToken()}catch(e){throw new o.Notice("Reauthentication required. Please reconnect to TickTick."),e}}}async createTicktickTask(e,t){let n=this.settings.accessToken;if(!n)return new o.Notice("No access token found. Please connect to TickTick in the settings."),{success:!1};let s="https://api.ticktick.com/open/v1/task",a={title:e,content:t};try{T(this,"task_creation_request",{endpoint:s,title:e});let i=await(0,o.requestUrl)({url:s,method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:JSON.stringify(a)});return i.status===200?{success:!0}:(console.error("TickTick API error (status):",i.status),console.log("response.json:",i.json),console.log("response.text:",i.text),{success:!1})}catch(i){return console.error("TickTick API error:",i),{success:!1}}}async loadSettings(){this.settings=Object.assign({},w,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}};
|
||||
|
|
|
|||
14
main.ts
14
main.ts
|
|
@ -83,6 +83,18 @@ export default class TickTickPlugin extends Plugin {
|
|||
await this.loadSettings();
|
||||
this.addSettingTab(new TickTickSettingTab(this.app, this));
|
||||
|
||||
this.registerObsidianProtocolHandler('ticktick-callback', async (params) => {
|
||||
if (!params.code) {
|
||||
new Notice('TickTick auth failed: no authorization code returned.');
|
||||
return;
|
||||
}
|
||||
if (!this.settings.tempState || params.state !== this.settings.tempState) {
|
||||
new Notice('TickTick auth failed: state mismatch. Please try connecting again.');
|
||||
return;
|
||||
}
|
||||
await this.exchangeAuthCodeForToken(params.code);
|
||||
});
|
||||
|
||||
// Command: Create TickTick task
|
||||
this.addCommand({
|
||||
id: 'create-ticktick-task',
|
||||
|
|
@ -218,6 +230,8 @@ export default class TickTickPlugin extends Plugin {
|
|||
|
||||
this.settings.accessToken = data.access_token;
|
||||
this.settings.tokenExpiry = Date.now() + (data.expires_in * 1000 * 0.85);
|
||||
this.settings.tempCodeVerifier = undefined;
|
||||
this.settings.tempState = undefined;
|
||||
await this.saveSettings();
|
||||
new Notice('TickTick access token obtained successfully!');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@
|
|||
"description": "Create TickTick tasks from text blocks with automatic Obsidian URI links. Requires Advanced URI plugin.",
|
||||
"author": "Muxin Li",
|
||||
"authorUrl": "https://github.com/heymoosh",
|
||||
"isDesktopOnly": true,
|
||||
"isDesktopOnly": false,
|
||||
"main": "main.js"
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "^4.21.2"
|
||||
|
|
|
|||
17
settings.ts
17
settings.ts
|
|
@ -131,26 +131,11 @@ export class TickTickSettingTab extends PluginSettingTab {
|
|||
|
||||
new Setting(containerEl)
|
||||
.setName('Connect to TickTick')
|
||||
.setDesc('Click to open the OAuth authorization URL in your browser.')
|
||||
.setDesc('Opens the OAuth authorization page in your browser. After approving, you will be returned to Obsidian automatically.')
|
||||
.addButton(async (button) => {
|
||||
button.setButtonText('Connect').onClick(async () => {
|
||||
await this.plugin.startAuthFlow();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Authorization code')
|
||||
.setDesc('Paste the authorization code from TickTick here to obtain an access token.')
|
||||
.addText(text =>
|
||||
text
|
||||
.setPlaceholder('Enter authorization code')
|
||||
.onChange(async (code) => {
|
||||
if (code.trim()) {
|
||||
await this.plugin.exchangeAuthCodeForToken(code.trim());
|
||||
text.setValue('');
|
||||
this.display();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue