oauth-callback 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.js +106 -306
- package/dist/mcp.js +106 -306
- package/dist/server.d.ts +6 -6
- package/dist/server.d.ts.map +1 -1
- package/package.json +14 -4
- package/src/server.ts +185 -450
package/README.md
CHANGED
|
@@ -413,6 +413,9 @@ bun test
|
|
|
413
413
|
# Build
|
|
414
414
|
bun run build
|
|
415
415
|
|
|
416
|
+
# Run documentation locally
|
|
417
|
+
bun run docs:dev # Start VitePress dev server at http://localhost:5173
|
|
418
|
+
|
|
416
419
|
# Run examples
|
|
417
420
|
bun run example:demo # Interactive demo
|
|
418
421
|
bun run example:github # GitHub OAuth example
|
package/dist/index.js
CHANGED
|
@@ -585,270 +585,129 @@ function renderError(params) {
|
|
|
585
585
|
|
|
586
586
|
// src/server.ts
|
|
587
587
|
function generateCallbackHTML(params, successHtml, errorHtml) {
|
|
588
|
-
if (params.error)
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
return successHtml || successTemplate;
|
|
588
|
+
if (!params.error)
|
|
589
|
+
return successHtml || successTemplate;
|
|
590
|
+
if (errorHtml)
|
|
591
|
+
return errorHtml.replace(/{{error}}/g, params.error || "").replace(/{{error_description}}/g, params.error_description || "").replace(/{{error_uri}}/g, params.error_uri || "");
|
|
592
|
+
return renderError({
|
|
593
|
+
error: params.error,
|
|
594
|
+
error_description: params.error_description,
|
|
595
|
+
error_uri: params.error_uri
|
|
596
|
+
});
|
|
599
597
|
}
|
|
600
598
|
|
|
601
|
-
class
|
|
602
|
-
|
|
603
|
-
callbackPromise;
|
|
604
|
-
callbackPath = "/callback";
|
|
599
|
+
class BaseCallbackServer {
|
|
600
|
+
callbackListeners = new Map;
|
|
605
601
|
successHtml;
|
|
606
602
|
errorHtml;
|
|
607
603
|
onRequest;
|
|
608
604
|
abortHandler;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
hostname = "localhost",
|
|
613
|
-
successHtml,
|
|
614
|
-
errorHtml,
|
|
615
|
-
signal,
|
|
616
|
-
onRequest
|
|
617
|
-
} = options;
|
|
605
|
+
signal;
|
|
606
|
+
setup(options) {
|
|
607
|
+
const { successHtml, errorHtml, signal, onRequest } = options;
|
|
618
608
|
this.successHtml = successHtml;
|
|
619
609
|
this.errorHtml = errorHtml;
|
|
620
610
|
this.onRequest = onRequest;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
this.callbackPromise.reject(new Error("Operation aborted"));
|
|
629
|
-
}
|
|
630
|
-
};
|
|
631
|
-
signal.addEventListener("abort", this.abortHandler);
|
|
632
|
-
}
|
|
633
|
-
this.server = Bun.serve({
|
|
634
|
-
port,
|
|
635
|
-
hostname,
|
|
636
|
-
fetch: (request) => this.handleRequest(request)
|
|
637
|
-
});
|
|
611
|
+
this.signal = signal;
|
|
612
|
+
if (!signal)
|
|
613
|
+
return;
|
|
614
|
+
if (signal.aborted)
|
|
615
|
+
throw new Error("Operation aborted");
|
|
616
|
+
this.abortHandler = () => this.stop();
|
|
617
|
+
signal.addEventListener("abort", this.abortHandler);
|
|
638
618
|
}
|
|
639
619
|
handleRequest(request) {
|
|
640
|
-
|
|
641
|
-
this.onRequest(request);
|
|
642
|
-
}
|
|
620
|
+
this.onRequest?.(request);
|
|
643
621
|
const url = new URL(request.url);
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
return new Response("Not Found", { status: 404 });
|
|
622
|
+
const listener = this.callbackListeners.get(url.pathname);
|
|
623
|
+
if (!listener)
|
|
624
|
+
return new Response("Not Found", { status: 404 });
|
|
625
|
+
const params = {};
|
|
626
|
+
for (const [key, value] of url.searchParams)
|
|
627
|
+
params[key] = value;
|
|
628
|
+
listener.resolve(params);
|
|
629
|
+
return new Response(generateCallbackHTML(params, this.successHtml, this.errorHtml), {
|
|
630
|
+
status: 200,
|
|
631
|
+
headers: { "Content-Type": "text/html" }
|
|
632
|
+
});
|
|
658
633
|
}
|
|
659
634
|
async waitForCallback(path2, timeout) {
|
|
660
|
-
this.
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
};
|
|
678
|
-
const wrappedReject = (error) => {
|
|
679
|
-
if (!isResolved) {
|
|
680
|
-
isResolved = true;
|
|
681
|
-
clearTimeout(timer);
|
|
682
|
-
this.callbackPromise = undefined;
|
|
683
|
-
reject(error);
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
|
|
687
|
-
});
|
|
635
|
+
if (this.callbackListeners.has(path2))
|
|
636
|
+
return Promise.reject(new Error(`A listener for the path "${path2}" is already active.`));
|
|
637
|
+
try {
|
|
638
|
+
return await Promise.race([
|
|
639
|
+
new Promise((resolve, reject) => {
|
|
640
|
+
this.callbackListeners.set(path2, { resolve, reject });
|
|
641
|
+
}),
|
|
642
|
+
new Promise((_, reject) => {
|
|
643
|
+
setTimeout(() => {
|
|
644
|
+
reject(new Error(`OAuth callback timeout after ${timeout}ms waiting for ${path2}`));
|
|
645
|
+
}, timeout);
|
|
646
|
+
})
|
|
647
|
+
]);
|
|
648
|
+
} finally {
|
|
649
|
+
this.callbackListeners.delete(path2);
|
|
650
|
+
}
|
|
688
651
|
}
|
|
689
652
|
async stop() {
|
|
690
|
-
if (this.abortHandler) {
|
|
691
|
-
|
|
692
|
-
if (signal) {
|
|
693
|
-
signal.removeEventListener("abort", this.abortHandler);
|
|
694
|
-
}
|
|
653
|
+
if (this.abortHandler && this.signal) {
|
|
654
|
+
this.signal.removeEventListener("abort", this.abortHandler);
|
|
695
655
|
this.abortHandler = undefined;
|
|
696
656
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (this.server) {
|
|
702
|
-
this.server.stop();
|
|
703
|
-
this.server = undefined;
|
|
704
|
-
}
|
|
657
|
+
for (const listener of this.callbackListeners.values())
|
|
658
|
+
listener.reject(new Error("Server stopped before callback received"));
|
|
659
|
+
this.callbackListeners.clear();
|
|
660
|
+
await this.stopServer();
|
|
705
661
|
}
|
|
706
662
|
}
|
|
707
663
|
|
|
708
|
-
class
|
|
664
|
+
class BunCallbackServer extends BaseCallbackServer {
|
|
709
665
|
server;
|
|
710
|
-
callbackPromise;
|
|
711
|
-
callbackPath = "/callback";
|
|
712
|
-
abortController;
|
|
713
|
-
successHtml;
|
|
714
|
-
errorHtml;
|
|
715
|
-
onRequest;
|
|
716
|
-
abortHandler;
|
|
717
666
|
async start(options) {
|
|
718
|
-
|
|
667
|
+
this.setup(options);
|
|
668
|
+
const { port, hostname = "localhost" } = options;
|
|
669
|
+
this.server = Bun.serve({
|
|
719
670
|
port,
|
|
720
|
-
hostname
|
|
721
|
-
|
|
722
|
-
errorHtml,
|
|
723
|
-
signal,
|
|
724
|
-
onRequest
|
|
725
|
-
} = options;
|
|
726
|
-
this.successHtml = successHtml;
|
|
727
|
-
this.errorHtml = errorHtml;
|
|
728
|
-
this.onRequest = onRequest;
|
|
729
|
-
this.abortController = new AbortController;
|
|
730
|
-
if (signal) {
|
|
731
|
-
if (signal.aborted) {
|
|
732
|
-
throw new Error("Operation aborted");
|
|
733
|
-
}
|
|
734
|
-
this.abortHandler = () => {
|
|
735
|
-
this.abortController?.abort();
|
|
736
|
-
if (this.callbackPromise) {
|
|
737
|
-
this.callbackPromise.reject(new Error("Operation aborted"));
|
|
738
|
-
}
|
|
739
|
-
};
|
|
740
|
-
signal.addEventListener("abort", this.abortHandler);
|
|
741
|
-
}
|
|
742
|
-
this.server = Deno.serve({ port, hostname, signal: this.abortController.signal }, (request) => this.handleRequest(request));
|
|
743
|
-
}
|
|
744
|
-
handleRequest(request) {
|
|
745
|
-
if (this.onRequest) {
|
|
746
|
-
this.onRequest(request);
|
|
747
|
-
}
|
|
748
|
-
const url = new URL(request.url);
|
|
749
|
-
if (url.pathname === this.callbackPath) {
|
|
750
|
-
const params = {};
|
|
751
|
-
for (const [key, value] of url.searchParams) {
|
|
752
|
-
params[key] = value;
|
|
753
|
-
}
|
|
754
|
-
if (this.callbackPromise) {
|
|
755
|
-
this.callbackPromise.resolve(params);
|
|
756
|
-
}
|
|
757
|
-
return new Response(generateCallbackHTML(params, this.successHtml, this.errorHtml), {
|
|
758
|
-
status: 200,
|
|
759
|
-
headers: { "Content-Type": "text/html" }
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
return new Response("Not Found", { status: 404 });
|
|
763
|
-
}
|
|
764
|
-
async waitForCallback(path2, timeout) {
|
|
765
|
-
this.callbackPath = path2;
|
|
766
|
-
return new Promise((resolve, reject) => {
|
|
767
|
-
let isResolved = false;
|
|
768
|
-
const timer = setTimeout(() => {
|
|
769
|
-
if (!isResolved) {
|
|
770
|
-
isResolved = true;
|
|
771
|
-
this.callbackPromise = undefined;
|
|
772
|
-
reject(new Error(`OAuth callback timeout after ${timeout}ms waiting for ${path2}`));
|
|
773
|
-
}
|
|
774
|
-
}, timeout);
|
|
775
|
-
const wrappedResolve = (result) => {
|
|
776
|
-
if (!isResolved) {
|
|
777
|
-
isResolved = true;
|
|
778
|
-
clearTimeout(timer);
|
|
779
|
-
this.callbackPromise = undefined;
|
|
780
|
-
resolve(result);
|
|
781
|
-
}
|
|
782
|
-
};
|
|
783
|
-
const wrappedReject = (error) => {
|
|
784
|
-
if (!isResolved) {
|
|
785
|
-
isResolved = true;
|
|
786
|
-
clearTimeout(timer);
|
|
787
|
-
this.callbackPromise = undefined;
|
|
788
|
-
reject(error);
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
|
|
671
|
+
hostname,
|
|
672
|
+
fetch: (request) => this.handleRequest(request)
|
|
792
673
|
});
|
|
793
674
|
}
|
|
794
|
-
async
|
|
795
|
-
if (this.
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
signal.removeEventListener("abort", this.abortHandler);
|
|
799
|
-
}
|
|
800
|
-
this.abortHandler = undefined;
|
|
801
|
-
}
|
|
802
|
-
if (this.callbackPromise) {
|
|
803
|
-
this.callbackPromise.reject(new Error("Server stopped before callback received"));
|
|
804
|
-
this.callbackPromise = undefined;
|
|
805
|
-
}
|
|
806
|
-
if (this.abortController) {
|
|
807
|
-
this.abortController.abort();
|
|
808
|
-
this.abortController = undefined;
|
|
809
|
-
}
|
|
675
|
+
async stopServer() {
|
|
676
|
+
if (!this.server)
|
|
677
|
+
return;
|
|
678
|
+
this.server.stop();
|
|
810
679
|
this.server = undefined;
|
|
811
680
|
}
|
|
812
681
|
}
|
|
813
682
|
|
|
814
|
-
class
|
|
683
|
+
class DenoCallbackServer extends BaseCallbackServer {
|
|
684
|
+
abortController;
|
|
685
|
+
async start(options) {
|
|
686
|
+
this.setup(options);
|
|
687
|
+
const { port, hostname = "localhost" } = options;
|
|
688
|
+
this.abortController = new AbortController;
|
|
689
|
+
options.signal?.addEventListener("abort", () => this.abortController?.abort());
|
|
690
|
+
Deno.serve({ port, hostname, signal: this.abortController.signal }, (request) => this.handleRequest(request));
|
|
691
|
+
}
|
|
692
|
+
async stopServer() {
|
|
693
|
+
if (!this.abortController)
|
|
694
|
+
return;
|
|
695
|
+
this.abortController.abort();
|
|
696
|
+
this.abortController = undefined;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
class NodeCallbackServer extends BaseCallbackServer {
|
|
815
701
|
server;
|
|
816
|
-
callbackPromise;
|
|
817
|
-
callbackPath = "/callback";
|
|
818
|
-
successHtml;
|
|
819
|
-
errorHtml;
|
|
820
|
-
onRequest;
|
|
821
|
-
abortHandler;
|
|
822
702
|
async start(options) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
hostname = "localhost",
|
|
826
|
-
successHtml,
|
|
827
|
-
errorHtml,
|
|
828
|
-
signal,
|
|
829
|
-
onRequest
|
|
830
|
-
} = options;
|
|
831
|
-
this.successHtml = successHtml;
|
|
832
|
-
this.errorHtml = errorHtml;
|
|
833
|
-
this.onRequest = onRequest;
|
|
834
|
-
if (signal) {
|
|
835
|
-
if (signal.aborted) {
|
|
836
|
-
throw new Error("Operation aborted");
|
|
837
|
-
}
|
|
838
|
-
this.abortHandler = () => {
|
|
839
|
-
this.stop();
|
|
840
|
-
if (this.callbackPromise) {
|
|
841
|
-
this.callbackPromise.reject(new Error("Operation aborted"));
|
|
842
|
-
}
|
|
843
|
-
};
|
|
844
|
-
signal.addEventListener("abort", this.abortHandler);
|
|
845
|
-
}
|
|
703
|
+
this.setup(options);
|
|
704
|
+
const { port, hostname = "localhost" } = options;
|
|
846
705
|
const { createServer } = await import("node:http");
|
|
847
706
|
return new Promise((resolve, reject) => {
|
|
848
707
|
this.server = createServer(async (req, res) => {
|
|
849
708
|
try {
|
|
850
|
-
const request = this.nodeToWebRequest(req, port);
|
|
851
|
-
const response =
|
|
709
|
+
const request = this.nodeToWebRequest(req, port, hostname);
|
|
710
|
+
const response = this.handleRequest(request);
|
|
852
711
|
res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
|
|
853
712
|
const body = await response.text();
|
|
854
713
|
res.end(body);
|
|
@@ -857,102 +716,43 @@ class NodeCallbackServer {
|
|
|
857
716
|
res.end("Internal Server Error");
|
|
858
717
|
}
|
|
859
718
|
});
|
|
719
|
+
if (options.signal)
|
|
720
|
+
options.signal.addEventListener("abort", () => this.server?.close());
|
|
860
721
|
this.server.listen(port, hostname, () => resolve());
|
|
861
722
|
this.server.on("error", reject);
|
|
862
723
|
});
|
|
863
724
|
}
|
|
864
|
-
|
|
865
|
-
|
|
725
|
+
async stopServer() {
|
|
726
|
+
if (!this.server)
|
|
727
|
+
return;
|
|
728
|
+
return new Promise((resolve) => {
|
|
729
|
+
this.server?.close(() => {
|
|
730
|
+
this.server = undefined;
|
|
731
|
+
resolve();
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
nodeToWebRequest(req, port, hostname) {
|
|
736
|
+
const host = req.headers.host || `${hostname}:${port}`;
|
|
737
|
+
const url = new URL(req.url, `http://${host}`);
|
|
866
738
|
const headers = new Headers;
|
|
867
739
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
868
|
-
if (typeof value === "string")
|
|
740
|
+
if (typeof value === "string")
|
|
869
741
|
headers.set(key, value);
|
|
870
|
-
|
|
742
|
+
else if (Array.isArray(value))
|
|
871
743
|
headers.set(key, value.join(", "));
|
|
872
|
-
}
|
|
873
744
|
}
|
|
874
745
|
return new Request(url.toString(), {
|
|
875
746
|
method: req.method,
|
|
876
747
|
headers
|
|
877
748
|
});
|
|
878
749
|
}
|
|
879
|
-
async handleRequest(request) {
|
|
880
|
-
if (this.onRequest) {
|
|
881
|
-
this.onRequest(request);
|
|
882
|
-
}
|
|
883
|
-
const url = new URL(request.url);
|
|
884
|
-
if (url.pathname === this.callbackPath) {
|
|
885
|
-
const params = {};
|
|
886
|
-
for (const [key, value] of url.searchParams) {
|
|
887
|
-
params[key] = value;
|
|
888
|
-
}
|
|
889
|
-
if (this.callbackPromise) {
|
|
890
|
-
this.callbackPromise.resolve(params);
|
|
891
|
-
}
|
|
892
|
-
return new Response(generateCallbackHTML(params, this.successHtml, this.errorHtml), {
|
|
893
|
-
status: 200,
|
|
894
|
-
headers: { "Content-Type": "text/html" }
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
return new Response("Not Found", { status: 404 });
|
|
898
|
-
}
|
|
899
|
-
async waitForCallback(path2, timeout) {
|
|
900
|
-
this.callbackPath = path2;
|
|
901
|
-
return new Promise((resolve, reject) => {
|
|
902
|
-
let isResolved = false;
|
|
903
|
-
const timer = setTimeout(() => {
|
|
904
|
-
if (!isResolved) {
|
|
905
|
-
isResolved = true;
|
|
906
|
-
this.callbackPromise = undefined;
|
|
907
|
-
reject(new Error(`OAuth callback timeout after ${timeout}ms waiting for ${path2}`));
|
|
908
|
-
}
|
|
909
|
-
}, timeout);
|
|
910
|
-
const wrappedResolve = (result) => {
|
|
911
|
-
if (!isResolved) {
|
|
912
|
-
isResolved = true;
|
|
913
|
-
clearTimeout(timer);
|
|
914
|
-
this.callbackPromise = undefined;
|
|
915
|
-
resolve(result);
|
|
916
|
-
}
|
|
917
|
-
};
|
|
918
|
-
const wrappedReject = (error) => {
|
|
919
|
-
if (!isResolved) {
|
|
920
|
-
isResolved = true;
|
|
921
|
-
clearTimeout(timer);
|
|
922
|
-
this.callbackPromise = undefined;
|
|
923
|
-
reject(error);
|
|
924
|
-
}
|
|
925
|
-
};
|
|
926
|
-
this.callbackPromise = { resolve: wrappedResolve, reject: wrappedReject };
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
async stop() {
|
|
930
|
-
if (this.abortHandler) {
|
|
931
|
-
const signal = this.server?.signal;
|
|
932
|
-
if (signal) {
|
|
933
|
-
signal.removeEventListener("abort", this.abortHandler);
|
|
934
|
-
}
|
|
935
|
-
this.abortHandler = undefined;
|
|
936
|
-
}
|
|
937
|
-
if (this.callbackPromise) {
|
|
938
|
-
this.callbackPromise.reject(new Error("Server stopped before callback received"));
|
|
939
|
-
this.callbackPromise = undefined;
|
|
940
|
-
}
|
|
941
|
-
if (this.server) {
|
|
942
|
-
return new Promise((resolve) => {
|
|
943
|
-
this.server.close(() => resolve());
|
|
944
|
-
this.server = undefined;
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
750
|
}
|
|
949
751
|
function createCallbackServer() {
|
|
950
|
-
if (typeof Bun !== "undefined")
|
|
752
|
+
if (typeof Bun !== "undefined")
|
|
951
753
|
return new BunCallbackServer;
|
|
952
|
-
|
|
953
|
-
if (typeof Deno !== "undefined") {
|
|
754
|
+
if (typeof Deno !== "undefined")
|
|
954
755
|
return new DenoCallbackServer;
|
|
955
|
-
}
|
|
956
756
|
return new NodeCallbackServer;
|
|
957
757
|
}
|
|
958
758
|
// src/storage/memory.ts
|